001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.mock.web.reactive.function.server;
018
019import java.net.InetSocketAddress;
020import java.net.URI;
021import java.nio.charset.Charset;
022import java.security.Principal;
023import java.util.Arrays;
024import java.util.Collections;
025import java.util.LinkedHashMap;
026import java.util.List;
027import java.util.Locale;
028import java.util.Map;
029import java.util.Optional;
030import java.util.OptionalLong;
031import java.util.concurrent.ConcurrentHashMap;
032
033import reactor.core.publisher.Flux;
034import reactor.core.publisher.Mono;
035
036import org.springframework.core.ParameterizedTypeReference;
037import org.springframework.http.HttpCookie;
038import org.springframework.http.HttpHeaders;
039import org.springframework.http.HttpMethod;
040import org.springframework.http.HttpRange;
041import org.springframework.http.MediaType;
042import org.springframework.http.codec.HttpMessageReader;
043import org.springframework.http.codec.multipart.Part;
044import org.springframework.http.server.PathContainer;
045import org.springframework.http.server.RequestPath;
046import org.springframework.http.server.reactive.ServerHttpRequest;
047import org.springframework.lang.Nullable;
048import org.springframework.util.Assert;
049import org.springframework.util.CollectionUtils;
050import org.springframework.util.LinkedMultiValueMap;
051import org.springframework.util.MultiValueMap;
052import org.springframework.web.reactive.function.BodyExtractor;
053import org.springframework.web.reactive.function.server.HandlerStrategies;
054import org.springframework.web.reactive.function.server.ServerRequest;
055import org.springframework.web.server.ServerWebExchange;
056import org.springframework.web.server.WebSession;
057import org.springframework.web.util.UriBuilder;
058import org.springframework.web.util.UriComponentsBuilder;
059
060/**
061 * Mock implementation of {@link ServerRequest}.
062 *
063 * @author Arjen Poutsma
064 * @since 5.0
065 */
066public final class MockServerRequest implements ServerRequest {
067
068        private final HttpMethod method;
069
070        private final URI uri;
071
072        private final RequestPath pathContainer;
073
074        private final MockHeaders headers;
075
076        private final MultiValueMap<String, HttpCookie> cookies;
077
078        @Nullable
079        private final Object body;
080
081        private final Map<String, Object> attributes;
082
083        private final MultiValueMap<String, String> queryParams;
084
085        private final Map<String, String> pathVariables;
086
087        @Nullable
088        private final WebSession session;
089
090        @Nullable
091        private Principal principal;
092
093        @Nullable
094        private final InetSocketAddress remoteAddress;
095
096        @Nullable
097        private final InetSocketAddress localAddress;
098
099        private final List<HttpMessageReader<?>> messageReaders;
100
101        @Nullable
102        private final ServerWebExchange exchange;
103
104
105        private MockServerRequest(HttpMethod method, URI uri, String contextPath, MockHeaders headers,
106                        MultiValueMap<String, HttpCookie> cookies, @Nullable Object body,
107                        Map<String, Object> attributes, MultiValueMap<String, String> queryParams,
108                        Map<String, String> pathVariables, @Nullable WebSession session, @Nullable Principal principal,
109                        @Nullable InetSocketAddress remoteAddress, @Nullable InetSocketAddress localAddress,
110                        List<HttpMessageReader<?>> messageReaders, @Nullable ServerWebExchange exchange) {
111
112                this.method = method;
113                this.uri = uri;
114                this.pathContainer = RequestPath.parse(uri, contextPath);
115                this.headers = headers;
116                this.cookies = cookies;
117                this.body = body;
118                this.attributes = attributes;
119                this.queryParams = queryParams;
120                this.pathVariables = pathVariables;
121                this.session = session;
122                this.principal = principal;
123                this.remoteAddress = remoteAddress;
124                this.localAddress = localAddress;
125                this.messageReaders = messageReaders;
126                this.exchange = exchange;
127        }
128
129
130        @Override
131        public HttpMethod method() {
132                return this.method;
133        }
134
135        @Override
136        public String methodName() {
137                return this.method.name();
138        }
139
140        @Override
141        public URI uri() {
142                return this.uri;
143        }
144
145        @Override
146        public UriBuilder uriBuilder() {
147                return UriComponentsBuilder.fromUri(this.uri);
148        }
149
150        @Override
151        public PathContainer pathContainer() {
152                return this.pathContainer;
153        }
154
155        @Override
156        public Headers headers() {
157                return this.headers;
158        }
159
160        @Override
161        public MultiValueMap<String, HttpCookie> cookies() {
162                return this.cookies;
163        }
164
165        @Override
166        public Optional<InetSocketAddress> remoteAddress() {
167                return Optional.ofNullable(this.remoteAddress);
168        }
169
170        @Override
171        public Optional<InetSocketAddress> localAddress() {
172                return Optional.ofNullable(this.localAddress);
173        }
174
175        @Override
176        public List<HttpMessageReader<?>> messageReaders() {
177                return this.messageReaders;
178        }
179
180        @Override
181        @SuppressWarnings("unchecked")
182        public <S> S body(BodyExtractor<S, ? super ServerHttpRequest> extractor) {
183                Assert.state(this.body != null, "No body");
184                return (S) this.body;
185        }
186
187        @Override
188        @SuppressWarnings("unchecked")
189        public <S> S body(BodyExtractor<S, ? super ServerHttpRequest> extractor, Map<String, Object> hints) {
190                Assert.state(this.body != null, "No body");
191                return (S) this.body;
192        }
193
194        @Override
195        @SuppressWarnings("unchecked")
196        public <S> Mono<S> bodyToMono(Class<? extends S> elementClass) {
197                Assert.state(this.body != null, "No body");
198                return (Mono<S>) this.body;
199        }
200
201        @Override
202        @SuppressWarnings("unchecked")
203        public <S> Mono<S> bodyToMono(ParameterizedTypeReference<S> typeReference) {
204                Assert.state(this.body != null, "No body");
205                return (Mono<S>) this.body;
206        }
207
208        @Override
209        @SuppressWarnings("unchecked")
210        public <S> Flux<S> bodyToFlux(Class<? extends S> elementClass) {
211                Assert.state(this.body != null, "No body");
212                return (Flux<S>) this.body;
213        }
214
215        @Override
216        @SuppressWarnings("unchecked")
217        public <S> Flux<S> bodyToFlux(ParameterizedTypeReference<S> typeReference) {
218                Assert.state(this.body != null, "No body");
219                return (Flux<S>) this.body;
220        }
221
222        @Override
223        public Map<String, Object> attributes() {
224                return this.attributes;
225        }
226
227        @Override
228        public MultiValueMap<String, String> queryParams() {
229                return CollectionUtils.unmodifiableMultiValueMap(this.queryParams);
230        }
231
232        @Override
233        public Map<String, String> pathVariables() {
234                return Collections.unmodifiableMap(this.pathVariables);
235        }
236
237        @Override
238        public Mono<WebSession> session() {
239                return Mono.justOrEmpty(this.session);
240        }
241
242        @Override
243        public Mono<? extends Principal> principal() {
244                return Mono.justOrEmpty(this.principal);
245        }
246
247        @Override
248        @SuppressWarnings("unchecked")
249        public Mono<MultiValueMap<String, String>> formData() {
250                Assert.state(this.body != null, "No body");
251                return (Mono<MultiValueMap<String, String>>) this.body;
252        }
253
254        @Override
255        @SuppressWarnings("unchecked")
256        public Mono<MultiValueMap<String, Part>> multipartData() {
257                Assert.state(this.body != null, "No body");
258                return (Mono<MultiValueMap<String, Part>>) this.body;
259        }
260
261        @Override
262        public ServerWebExchange exchange() {
263                Assert.state(this.exchange != null, "No exchange");
264                return this.exchange;
265        }
266
267        public static Builder builder() {
268                return new BuilderImpl();
269        }
270
271        /**
272         * Builder for {@link MockServerRequest}.
273         */
274        public interface Builder {
275
276                Builder method(HttpMethod method);
277
278                Builder uri(URI uri);
279
280                Builder contextPath(String contextPath);
281
282                Builder header(String key, String value);
283
284                Builder headers(HttpHeaders headers);
285
286                Builder cookie(HttpCookie... cookies);
287
288                Builder cookies(MultiValueMap<String, HttpCookie> cookies);
289
290                Builder attribute(String name, Object value);
291
292                Builder attributes(Map<String, Object> attributes);
293
294                Builder queryParam(String key, String value);
295
296                Builder queryParams(MultiValueMap<String, String> queryParams);
297
298                Builder pathVariable(String key, String value);
299
300                Builder pathVariables(Map<String, String> pathVariables);
301
302                Builder session(WebSession session);
303
304                /**
305                 * Sets the request {@link Principal}.
306                 * @deprecated in favor of {@link #principal(Principal)}
307                 */
308                @Deprecated
309                Builder session(Principal principal);
310
311                Builder principal(Principal principal);
312
313                Builder remoteAddress(InetSocketAddress remoteAddress);
314
315                Builder localAddress(InetSocketAddress localAddress);
316
317                Builder messageReaders(List<HttpMessageReader<?>> messageReaders);
318
319                Builder exchange(ServerWebExchange exchange);
320
321                MockServerRequest body(Object body);
322
323                MockServerRequest build();
324        }
325
326
327        private static class BuilderImpl implements Builder {
328
329                private HttpMethod method = HttpMethod.GET;
330
331                private URI uri = URI.create("http://localhost");
332
333                private String contextPath = "";
334
335                private MockHeaders headers = new MockHeaders(new HttpHeaders());
336
337                private MultiValueMap<String, HttpCookie> cookies = new LinkedMultiValueMap<>();
338
339                @Nullable
340                private Object body;
341
342                private Map<String, Object> attributes = new ConcurrentHashMap<>();
343
344                private MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
345
346                private Map<String, String> pathVariables = new LinkedHashMap<>();
347
348                @Nullable
349                private WebSession session;
350
351                @Nullable
352                private Principal principal;
353
354                @Nullable
355                private InetSocketAddress remoteAddress;
356
357                @Nullable
358                private InetSocketAddress localAddress;
359
360                private List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders();
361
362                @Nullable
363                private ServerWebExchange exchange;
364
365                @Override
366                public Builder method(HttpMethod method) {
367                        Assert.notNull(method, "'method' must not be null");
368                        this.method = method;
369                        return this;
370                }
371
372                @Override
373                public Builder uri(URI uri) {
374                        Assert.notNull(uri, "'uri' must not be null");
375                        this.uri = uri;
376                        return this;
377                }
378
379                @Override
380                public Builder contextPath(String contextPath) {
381                        Assert.notNull(contextPath, "'contextPath' must not be null");
382                        this.contextPath = contextPath;
383                        return this;
384
385                }
386
387                @Override
388                public Builder cookie(HttpCookie... cookies) {
389                        Arrays.stream(cookies).forEach(cookie -> this.cookies.add(cookie.getName(), cookie));
390                        return this;
391                }
392
393                @Override
394                public Builder cookies(MultiValueMap<String, HttpCookie> cookies) {
395                        Assert.notNull(cookies, "'cookies' must not be null");
396                        this.cookies = cookies;
397                        return this;
398                }
399
400                @Override
401                public Builder header(String key, String value) {
402                        Assert.notNull(key, "'key' must not be null");
403                        Assert.notNull(value, "'value' must not be null");
404                        this.headers.header(key, value);
405                        return this;
406                }
407
408                @Override
409                public Builder headers(HttpHeaders headers) {
410                        Assert.notNull(headers, "'headers' must not be null");
411                        this.headers = new MockHeaders(headers);
412                        return this;
413                }
414
415                @Override
416                public Builder attribute(String name, Object value) {
417                        Assert.notNull(name, "'name' must not be null");
418                        Assert.notNull(value, "'value' must not be null");
419                        this.attributes.put(name, value);
420                        return this;
421                }
422
423                @Override
424                public Builder attributes(Map<String, Object> attributes) {
425                        Assert.notNull(attributes, "'attributes' must not be null");
426                        this.attributes = attributes;
427                        return this;
428                }
429
430                @Override
431                public Builder queryParam(String key, String value) {
432                        Assert.notNull(key, "'key' must not be null");
433                        Assert.notNull(value, "'value' must not be null");
434                        this.queryParams.add(key, value);
435                        return this;
436                }
437
438                @Override
439                public Builder queryParams(MultiValueMap<String, String> queryParams) {
440                        Assert.notNull(queryParams, "'queryParams' must not be null");
441                        this.queryParams = queryParams;
442                        return this;
443                }
444
445                @Override
446                public Builder pathVariable(String key, String value) {
447                        Assert.notNull(key, "'key' must not be null");
448                        Assert.notNull(value, "'value' must not be null");
449                        this.pathVariables.put(key, value);
450                        return this;
451                }
452
453                @Override
454                public Builder pathVariables(Map<String, String> pathVariables) {
455                        Assert.notNull(pathVariables, "'pathVariables' must not be null");
456                        this.pathVariables = pathVariables;
457                        return this;
458                }
459
460                @Override
461                public Builder session(WebSession session) {
462                        Assert.notNull(session, "'session' must not be null");
463                        this.session = session;
464                        return this;
465                }
466
467                @Override
468                @Deprecated
469                public Builder session(Principal principal) {
470                        return principal(principal);
471                }
472
473                @Override
474                public Builder principal(Principal principal) {
475                        Assert.notNull(principal, "'principal' must not be null");
476                        this.principal = principal;
477                        return this;
478                }
479
480                @Override
481                public Builder remoteAddress(InetSocketAddress remoteAddress) {
482                        Assert.notNull(remoteAddress, "'remoteAddress' must not be null");
483                        this.remoteAddress = remoteAddress;
484                        return this;
485                }
486
487                @Override
488                public Builder localAddress(InetSocketAddress localAddress) {
489                        Assert.notNull(localAddress, "'localAddress' must not be null");
490                        this.localAddress = localAddress;
491                        return this;
492                }
493
494                @Override
495                public Builder messageReaders(List<HttpMessageReader<?>> messageReaders) {
496                        Assert.notNull(messageReaders, "'messageReaders' must not be null");
497                        this.messageReaders = messageReaders;
498                        return this;
499                }
500
501                @Override
502                public Builder exchange(ServerWebExchange exchange) {
503                        Assert.notNull(exchange, "'exchange' must not be null");
504                        this.exchange = exchange;
505                        return this;
506                }
507
508                @Override
509                public MockServerRequest body(Object body) {
510                        this.body = body;
511                        return new MockServerRequest(this.method, this.uri, this.contextPath, this.headers,
512                                        this.cookies, this.body, this.attributes, this.queryParams, this.pathVariables,
513                                        this.session, this.principal, this.remoteAddress, this.localAddress,
514                                        this.messageReaders, this.exchange);
515                }
516
517                @Override
518                public MockServerRequest build() {
519                        return new MockServerRequest(this.method, this.uri, this.contextPath, this.headers,
520                                        this.cookies, null, this.attributes, this.queryParams, this.pathVariables,
521                                        this.session, this.principal, this.remoteAddress, this.localAddress,
522                                        this.messageReaders, this.exchange);
523                }
524        }
525
526
527        private static class MockHeaders implements Headers {
528
529                private final HttpHeaders headers;
530
531                public MockHeaders(HttpHeaders headers) {
532                        this.headers = headers;
533                }
534
535                private HttpHeaders delegate() {
536                        return this.headers;
537                }
538
539                public void header(String key, String value) {
540                        this.headers.add(key, value);
541                }
542
543                @Override
544                public List<MediaType> accept() {
545                        return delegate().getAccept();
546                }
547
548                @Override
549                public List<Charset> acceptCharset() {
550                        return delegate().getAcceptCharset();
551                }
552
553                @Override
554                public List<Locale.LanguageRange> acceptLanguage() {
555                        return delegate().getAcceptLanguage();
556                }
557
558                @Override
559                public OptionalLong contentLength() {
560                        return toOptionalLong(delegate().getContentLength());
561                }
562
563                @Override
564                public Optional<MediaType> contentType() {
565                        return Optional.ofNullable(delegate().getContentType());
566                }
567
568                @Override
569                public InetSocketAddress host() {
570                        return delegate().getHost();
571                }
572
573                @Override
574                public List<HttpRange> range() {
575                        return delegate().getRange();
576                }
577
578                @Override
579                public List<String> header(String headerName) {
580                        List<String> headerValues = delegate().get(headerName);
581                        return headerValues != null ? headerValues : Collections.emptyList();
582                }
583
584                @Override
585                public HttpHeaders asHttpHeaders() {
586                        return HttpHeaders.readOnlyHttpHeaders(delegate());
587                }
588
589                private OptionalLong toOptionalLong(long value) {
590                        return value != -1 ? OptionalLong.of(value) : OptionalLong.empty();
591                }
592
593        }
594
595}