diff --git a/spring-test/src/main/java/org/springframework/mock/web/reactive/function/server/MockServerRequest.java b/spring-test/src/main/java/org/springframework/mock/web/reactive/function/server/MockServerRequest.java index 2894475d9c..2e250724cc 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/reactive/function/server/MockServerRequest.java +++ b/spring-test/src/main/java/org/springframework/mock/web/reactive/function/server/MockServerRequest.java @@ -19,6 +19,7 @@ package org.springframework.mock.web.reactive.function.server; import java.net.InetSocketAddress; import java.net.URI; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.security.Principal; import java.util.Arrays; import java.util.Collections; @@ -38,6 +39,8 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRange; import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.PathContainer; +import org.springframework.http.server.reactive.RequestPath; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -59,6 +62,8 @@ public class MockServerRequest implements ServerRequest { private final URI uri; + private final RequestPath pathContainer; + private final MockHeaders headers; private final MultiValueMap cookies; @@ -79,13 +84,14 @@ public class MockServerRequest implements ServerRequest { private Principal principal; - private MockServerRequest(HttpMethod method, URI uri, MockHeaders headers, + private MockServerRequest(HttpMethod method, URI uri, String contextPath, MockHeaders headers, MultiValueMap cookies, @Nullable Object body, Map attributes, MultiValueMap queryParams, Map pathVariables, @Nullable WebSession session, @Nullable Principal principal) { this.method = method; this.uri = uri; + this.pathContainer = RequestPath.create(uri, contextPath, StandardCharsets.UTF_8); this.headers = headers; this.cookies = cookies; this.body = body; @@ -107,6 +113,11 @@ public class MockServerRequest implements ServerRequest { return this.uri; } + @Override + public PathContainer pathContainer() { + return this.pathContainer; + } + @Override public Headers headers() { return this.headers; @@ -189,6 +200,8 @@ public class MockServerRequest implements ServerRequest { Builder uri(URI uri); + Builder contextPath(String contextPath); + Builder header(String key, String value); Builder headers(HttpHeaders headers); @@ -225,6 +238,8 @@ public class MockServerRequest implements ServerRequest { private URI uri = URI.create("http://localhost"); + private String contextPath = ""; + private MockHeaders headers = new MockHeaders(new HttpHeaders()); private MultiValueMap cookies = new LinkedMultiValueMap<>(); @@ -258,6 +273,14 @@ public class MockServerRequest implements ServerRequest { return this; } + @Override + public Builder contextPath(String contextPath) { + Assert.notNull(contextPath, "'contextPath' must not be null"); + this.contextPath = contextPath; + return this; + + } + @Override public Builder cookie(HttpCookie... cookies) { Arrays.stream(cookies).forEach(cookie -> this.cookies.add(cookie.getName(), cookie)); @@ -348,16 +371,16 @@ public class MockServerRequest implements ServerRequest { @Override public MockServerRequest body(Object body) { this.body = body; - return new MockServerRequest(this.method, this.uri, this.headers, this.cookies, - this.body, this.attributes, this.queryParams, this.pathVariables, this.session, - this.principal); + return new MockServerRequest(this.method, this.uri, this.contextPath, this.headers, + this.cookies, this.body, this.attributes, this.queryParams, this.pathVariables, + this.session, this.principal); } @Override public MockServerRequest build() { - return new MockServerRequest(this.method, this.uri, this.headers, this.cookies, null, - this.attributes, this.queryParams, this.pathVariables, this.session, - this.principal); + return new MockServerRequest(this.method, this.uri, this.contextPath, this.headers, + this.cookies, null, this.attributes, this.queryParams, this.pathVariables, + this.session, this.principal); } } diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/RequestPath.java b/spring-web/src/main/java/org/springframework/http/server/reactive/RequestPath.java index 7228ca2933..e63489e4f3 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/RequestPath.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/RequestPath.java @@ -15,6 +15,9 @@ */ package org.springframework.http.server.reactive; +import java.net.URI; +import java.nio.charset.Charset; + /** * Represents the complete path for a request. * @@ -38,4 +41,11 @@ public interface RequestPath extends PathContainer { */ PathContainer pathWithinApplication(); + /** + * Create a new {@code RequestPath} with the given parameters. + */ + static RequestPath create(URI uri, String contextPath, Charset charset) { + return new DefaultRequestPath(uri, contextPath, charset); + } + } diff --git a/spring-web/src/main/java/org/springframework/web/util/pattern/PathPattern.java b/spring-web/src/main/java/org/springframework/web/util/pattern/PathPattern.java index a6c560eaf6..a3babd99b6 100644 --- a/spring-web/src/main/java/org/springframework/web/util/pattern/PathPattern.java +++ b/spring-web/src/main/java/org/springframework/web/util/pattern/PathPattern.java @@ -539,8 +539,8 @@ public class PathPattern implements Comparable { /** * Return the part of a path that was not matched by a pattern. */ - public String getPathRemaining() { - return this.pathRemaining.value(); + public PathContainer getPathRemaining() { + return this.pathRemaining; } /** diff --git a/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternMatcherTests.java b/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternMatcherTests.java index 5c93949c12..3ace116f64 100644 --- a/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternMatcherTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternMatcherTests.java @@ -37,13 +37,7 @@ import org.springframework.web.util.pattern.PathPattern.PathMatchResult; import org.springframework.web.util.pattern.PathPattern.PathRemainingMatchInfo; import static org.hamcrest.CoreMatchers.containsString; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; /** * Exercise matching of {@link PathPattern} objects. @@ -274,22 +268,22 @@ public class PathPatternMatcherTests { @Test public void pathRemainderBasicCases_spr15336() { // Cover all PathElement kinds - assertEquals("/bar", getPathRemaining("/foo","/foo/bar").getPathRemaining()); - assertEquals("/", getPathRemaining("/foo","/foo/").getPathRemaining()); - assertEquals("/bar",getPathRemaining("/foo*","/foo/bar").getPathRemaining()); - assertEquals("/bar", getPathRemaining("/*","/foo/bar").getPathRemaining()); - assertEquals("/bar", getPathRemaining("/{foo}","/foo/bar").getPathRemaining()); + assertEquals("/bar", getPathRemaining("/foo","/foo/bar").getPathRemaining().value()); + assertEquals("/", getPathRemaining("/foo","/foo/").getPathRemaining().value()); + assertEquals("/bar",getPathRemaining("/foo*","/foo/bar").getPathRemaining().value()); + assertEquals("/bar", getPathRemaining("/*","/foo/bar").getPathRemaining().value()); + assertEquals("/bar", getPathRemaining("/{foo}","/foo/bar").getPathRemaining().value()); assertNull(getPathRemaining("/foo","/bar/baz")); - assertEquals("",getPathRemaining("/**","/foo/bar").getPathRemaining()); - assertEquals("",getPathRemaining("/{*bar}","/foo/bar").getPathRemaining()); - assertEquals("/bar",getPathRemaining("/a?b/d?e","/aab/dde/bar").getPathRemaining()); - assertEquals("/bar",getPathRemaining("/{abc}abc","/xyzabc/bar").getPathRemaining()); - assertEquals("/bar",getPathRemaining("/*y*","/xyzxyz/bar").getPathRemaining()); - assertEquals("",getPathRemaining("/","/").getPathRemaining()); - assertEquals("a",getPathRemaining("/","/a").getPathRemaining()); - assertEquals("a/",getPathRemaining("/","/a/").getPathRemaining()); - assertEquals("/bar",getPathRemaining("/a{abc}","/a/bar").getPathRemaining()); - assertEquals("/bar", getPathRemaining("/foo//","/foo///bar").getPathRemaining()); + assertEquals("",getPathRemaining("/**","/foo/bar").getPathRemaining().value()); + assertEquals("",getPathRemaining("/{*bar}","/foo/bar").getPathRemaining().value()); + assertEquals("/bar",getPathRemaining("/a?b/d?e","/aab/dde/bar").getPathRemaining().value()); + assertEquals("/bar",getPathRemaining("/{abc}abc","/xyzabc/bar").getPathRemaining().value()); + assertEquals("/bar",getPathRemaining("/*y*","/xyzxyz/bar").getPathRemaining().value()); + assertEquals("",getPathRemaining("/","/").getPathRemaining().value()); + assertEquals("a",getPathRemaining("/","/a").getPathRemaining().value()); + assertEquals("a/",getPathRemaining("/","/a/").getPathRemaining().value()); + assertEquals("/bar",getPathRemaining("/a{abc}","/a/bar").getPathRemaining().value()); + assertEquals("/bar", getPathRemaining("/foo//","/foo///bar").getPathRemaining().value()); } @Test @@ -333,32 +327,32 @@ public class PathPatternMatcherTests { // With a /** on the end have to check if there is any more data post // 'the match' it starts with a separator assertNull(parse("/resource/**").getPathRemaining(toPathContainer("/resourceX"))); - assertEquals("",parse("/resource/**").getPathRemaining(toPathContainer("/resource")).getPathRemaining()); + assertEquals("",parse("/resource/**").getPathRemaining(toPathContainer("/resource")).getPathRemaining().value()); // Similar to above for the capture-the-rest variant assertNull(parse("/resource/{*foo}").getPathRemaining(toPathContainer("/resourceX"))); - assertEquals("",parse("/resource/{*foo}").getPathRemaining(toPathContainer("/resource")).getPathRemaining()); + assertEquals("",parse("/resource/{*foo}").getPathRemaining(toPathContainer("/resource")).getPathRemaining().value()); PathPattern.PathRemainingMatchInfo pri = parse("/aaa/{bbb}/c?d/e*f/*/g").getPathRemaining(toPathContainer("/aaa/b/ccd/ef/x/g/i")); assertNotNull(pri); - assertEquals("/i",pri.getPathRemaining()); + assertEquals("/i",pri.getPathRemaining().value()); assertEquals("b",pri.getUriVariables().get("bbb")); pri = parse("/aaa/{bbb}/c?d/e*f/*/g/").getPathRemaining(toPathContainer("/aaa/b/ccd/ef/x/g/i")); assertNotNull(pri); - assertEquals("i",pri.getPathRemaining()); + assertEquals("i",pri.getPathRemaining().value()); assertEquals("b",pri.getUriVariables().get("bbb")); pri = parse("/{aaa}_{bbb}/e*f/{x}/g").getPathRemaining(toPathContainer("/aa_bb/ef/x/g/i")); assertNotNull(pri); - assertEquals("/i",pri.getPathRemaining()); + assertEquals("/i",pri.getPathRemaining().value()); assertEquals("aa",pri.getUriVariables().get("aaa")); assertEquals("bb",pri.getUriVariables().get("bbb")); assertEquals("x",pri.getUriVariables().get("x")); assertNull(parse("/a/b").getPathRemaining(toPathContainer(""))); - assertEquals("/a/b",parse("").getPathRemaining(toPathContainer("/a/b")).getPathRemaining()); - assertEquals("",parse("").getPathRemaining(toPathContainer("")).getPathRemaining()); + assertEquals("/a/b",parse("").getPathRemaining(toPathContainer("/a/b")).getPathRemaining().value()); + assertEquals("",parse("").getPathRemaining(toPathContainer("")).getPathRemaining().value()); } @Test @@ -554,30 +548,30 @@ public class PathPatternMatcherTests { // It would be nice to partially match a path and get any bound variables in one step pp = parse("/{this}/{one}/{here}"); pri = getPathRemaining(pp, "/foo/bar/goo/boo"); - assertEquals("/boo",pri.getPathRemaining()); + assertEquals("/boo",pri.getPathRemaining().value()); assertEquals("foo",pri.getUriVariables().get("this")); assertEquals("bar",pri.getUriVariables().get("one")); assertEquals("goo",pri.getUriVariables().get("here")); pp = parse("/aaa/{foo}"); pri = getPathRemaining(pp, "/aaa/bbb"); - assertEquals("",pri.getPathRemaining()); + assertEquals("",pri.getPathRemaining().value()); assertEquals("bbb",pri.getUriVariables().get("foo")); pp = parse("/aaa/bbb"); pri = getPathRemaining(pp, "/aaa/bbb"); - assertEquals("",pri.getPathRemaining()); + assertEquals("",pri.getPathRemaining().value()); assertEquals(0,pri.getUriVariables().size()); pp = parse("/*/{foo}/b*"); pri = getPathRemaining(pp, "/foo"); assertNull(pri); pri = getPathRemaining(pp, "/abc/def/bhi"); - assertEquals("",pri.getPathRemaining()); + assertEquals("",pri.getPathRemaining().value()); assertEquals("def",pri.getUriVariables().get("foo")); pri = getPathRemaining(pp, "/abc/def/bhi/jkl"); - assertEquals("/jkl",pri.getPathRemaining()); + assertEquals("/jkl",pri.getPathRemaining().value()); assertEquals("def",pri.getUriVariables().get("foo")); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java index 26d16d9b17..e42c7a28f2 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/DefaultServerRequest.java @@ -38,6 +38,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.HttpRange; import org.springframework.http.MediaType; import org.springframework.http.codec.HttpMessageReader; +import org.springframework.http.server.reactive.PathContainer; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.util.Assert; @@ -92,6 +93,11 @@ class DefaultServerRequest implements ServerRequest { return request().getURI(); } + @Override + public PathContainer pathContainer() { + return request().getPath(); + } + @Override public Headers headers() { return this.headers; diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java index a4562087c8..e0abca1346 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/RequestPredicates.java @@ -18,6 +18,7 @@ package org.springframework.web.reactive.function.server; import java.net.URI; import java.security.Principal; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -37,6 +38,7 @@ import reactor.core.publisher.Mono; import org.springframework.http.HttpCookie; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.PathContainer; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -339,9 +341,8 @@ public abstract class RequestPredicates { @Override public boolean test(ServerRequest request) { - String path = request.path(); - boolean match = this.pattern.matches(path); - traceMatch("Pattern", this.pattern.getPatternString(), path, match); + boolean match = this.pattern.matches(request.pathContainer()); + traceMatch("Pattern", this.pattern.getPatternString(), request.path(), match); if (match) { mergeTemplateVariables(request, this.pattern.matchAndExtract(request.path()).getUriVariables()); return true; @@ -353,14 +354,10 @@ public abstract class RequestPredicates { @Override public Optional nest(ServerRequest request) { - return Optional.ofNullable(this.pattern.getPathRemaining(request.path())) + return Optional.ofNullable(this.pattern.getPathRemaining(request.pathContainer())) .map(info -> { mergeTemplateVariables(request, info.getUriVariables()); - String path = info.getPathRemaining(); - if (!path.startsWith("/")) { - path = "/" + path; - } - return new SubPathServerRequestWrapper(request, path); + return new SubPathServerRequestWrapper(request, info); }); } @@ -464,12 +461,12 @@ public abstract class RequestPredicates { private final ServerRequest request; - private final String subPath; + private final PathContainer subPathContainer; - public SubPathServerRequestWrapper(ServerRequest request, String subPath) { + public SubPathServerRequestWrapper(ServerRequest request, PathPattern.PathRemainingMatchInfo info) { this.request = request; - this.subPath = subPath; + this.subPathContainer = new SubPathContainer(info.getPathRemaining()); } @Override @@ -484,7 +481,12 @@ public abstract class RequestPredicates { @Override public String path() { - return this.subPath; + return this.subPathContainer.value(); + } + + @Override + public PathContainer pathContainer() { + return this.subPathContainer; } @Override @@ -561,6 +563,47 @@ public abstract class RequestPredicates { public String toString() { return method() + " " + path(); } + + private static class SubPathContainer implements PathContainer { + + private static final PathContainer.Separator SEPARATOR = () -> "/"; + + + private final String value; + + private final List elements; + + public SubPathContainer(PathContainer original) { + this.value = prefixWithSlash(original.value()); + this.elements = prependWithSeparator(original.elements()); + } + + private static String prefixWithSlash(String path) { + if (!path.startsWith("/")) { + path = "/" + path; + } + return path; + } + + private static List prependWithSeparator(List elements) { + List result = new ArrayList<>(elements); + if (!(result.get(0) instanceof Separator)) { + result.add(0, SEPARATOR); + } + return Collections.unmodifiableList(result); + } + + + @Override + public String value() { + return this.value; + } + + @Override + public List elements() { + return this.elements; + } + } } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerRequest.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerRequest.java index c5e0b1ab36..16b9fc031a 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerRequest.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/ServerRequest.java @@ -19,6 +19,7 @@ package org.springframework.web.reactive.function.server; import java.net.InetSocketAddress; import java.net.URI; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.security.Principal; import java.util.List; import java.util.Locale; @@ -36,6 +37,7 @@ import org.springframework.http.HttpRange; import org.springframework.http.MediaType; import org.springframework.http.codec.HttpMessageReader; import org.springframework.http.codec.json.Jackson2CodecSupport; +import org.springframework.http.server.reactive.PathContainer; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.lang.Nullable; import org.springframework.util.MultiValueMap; @@ -72,6 +74,13 @@ public interface ServerRequest { return uri().getRawPath(); } + /** + * Return the request path as {@code PathContainer}. + */ + default PathContainer pathContainer() { + return PathContainer.parse(path(), StandardCharsets.UTF_8); + } + /** * Return the headers of this request. */ diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/ServerRequestWrapper.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/ServerRequestWrapper.java index 8b87c87d05..170f1edd5e 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/ServerRequestWrapper.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/server/support/ServerRequestWrapper.java @@ -34,6 +34,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRange; import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.PathContainer; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; @@ -87,6 +88,11 @@ public class ServerRequestWrapper implements ServerRequest { return this.delegate.path(); } + @Override + public PathContainer pathContainer() { + return this.delegate.pathContainer(); + } + @Override public Headers headers() { return this.delegate.headers(); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/MockServerRequest.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/MockServerRequest.java index 929dc6b1b7..f317abc7df 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/MockServerRequest.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/MockServerRequest.java @@ -19,6 +19,7 @@ package org.springframework.web.reactive.function.server; import java.net.InetSocketAddress; import java.net.URI; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.security.Principal; import java.util.Arrays; import java.util.Collections; @@ -38,6 +39,8 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRange; import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.PathContainer; +import org.springframework.http.server.reactive.RequestPath; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -48,6 +51,7 @@ import org.springframework.web.server.WebSession; /** * Mock implementation of {@link ServerRequest}. + * * @author Arjen Poutsma * @since 5.0 */ @@ -57,6 +61,8 @@ public class MockServerRequest implements ServerRequest { private final URI uri; + private final RequestPath pathContainer; + private final MockHeaders headers; private final MultiValueMap cookies; @@ -76,13 +82,15 @@ public class MockServerRequest implements ServerRequest { @Nullable private Principal principal; - private MockServerRequest(HttpMethod method, URI uri, - MockHeaders headers, MultiValueMap cookies, @Nullable Object body, + + private MockServerRequest(HttpMethod method, URI uri, String contextPath, MockHeaders headers, + MultiValueMap cookies, @Nullable Object body, Map attributes, MultiValueMap queryParams, Map pathVariables, @Nullable WebSession session, @Nullable Principal principal) { this.method = method; this.uri = uri; + this.pathContainer = RequestPath.create(uri, contextPath, StandardCharsets.UTF_8); this.headers = headers; this.cookies = cookies; this.body = body; @@ -104,6 +112,11 @@ public class MockServerRequest implements ServerRequest { return this.uri; } + @Override + public PathContainer pathContainer() { + return this.pathContainer; + } + @Override public Headers headers() { return this.headers; @@ -116,7 +129,7 @@ public class MockServerRequest implements ServerRequest { @Override @SuppressWarnings("unchecked") - public S body(BodyExtractor extractor){ + public S body(BodyExtractor extractor) { Assert.state(this.body != null, "No body"); return (S) this.body; } @@ -186,6 +199,8 @@ public class MockServerRequest implements ServerRequest { Builder uri(URI uri); + Builder contextPath(String contextPath); + Builder header(String key, String value); Builder headers(HttpHeaders headers); @@ -222,6 +237,8 @@ public class MockServerRequest implements ServerRequest { private URI uri = URI.create("http://localhost"); + private String contextPath = ""; + private MockHeaders headers = new MockHeaders(new HttpHeaders()); private MultiValueMap cookies = new LinkedMultiValueMap<>(); @@ -256,18 +273,11 @@ public class MockServerRequest implements ServerRequest { } @Override - public Builder header(String key, String value) { - Assert.notNull(key, "'key' must not be null"); - Assert.notNull(value, "'value' must not be null"); - this.headers.header(key, value); + public Builder contextPath(String contextPath) { + Assert.notNull(contextPath, "'contextPath' must not be null"); + this.contextPath = contextPath; return this; - } - @Override - public Builder headers(HttpHeaders headers) { - Assert.notNull(headers, "'headers' must not be null"); - this.headers = new MockHeaders(headers); - return this; } @Override @@ -283,6 +293,21 @@ public class MockServerRequest implements ServerRequest { return this; } + @Override + public Builder header(String key, String value) { + Assert.notNull(key, "'key' must not be null"); + Assert.notNull(value, "'value' must not be null"); + this.headers.header(key, value); + return this; + } + + @Override + public Builder headers(HttpHeaders headers) { + Assert.notNull(headers, "'headers' must not be null"); + this.headers = new MockHeaders(headers); + return this; + } + @Override public Builder attribute(String name, Object value) { Assert.notNull(name, "'name' must not be null"); @@ -345,16 +370,16 @@ public class MockServerRequest implements ServerRequest { @Override public MockServerRequest body(Object body) { this.body = body; - return new MockServerRequest(this.method, this.uri, this.headers, this.cookies, - this.body, this.attributes, this.queryParams, this.pathVariables, this.session, - this.principal); + return new MockServerRequest(this.method, this.uri, this.contextPath, this.headers, + this.cookies, this.body, this.attributes, this.queryParams, this.pathVariables, + this.session, this.principal); } @Override public MockServerRequest build() { - return new MockServerRequest(this.method, this.uri, this.headers, this.cookies, null, - this.attributes, this.queryParams, this.pathVariables, this.session, - this.principal); + return new MockServerRequest(this.method, this.uri, this.contextPath, this.headers, + this.cookies, null, this.attributes, this.queryParams, this.pathVariables, + this.session, this.principal); } }