Browse Source

Add basic support for @RequestMapping

Just enough for a test with an @ResponseBody method that accepts an
@RequestParam String arg and returning Publisher<String> or String.

See RequestMappingIntegrationTests.
pull/1111/head
Rossen Stoyanchev 11 years ago
parent
commit
202825554c
  1. 6
      spring-web-reactive/build.gradle
  2. 43
      spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/DispatcherHandler.java
  3. 18
      spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/HandlerResult.java
  4. 32
      spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/HandlerMethodArgumentResolver.java
  5. 184
      spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/InvocableHandlerMethod.java
  6. 79
      spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/annotation/RequestMappingHandlerAdapter.java
  7. 91
      spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/annotation/RequestMappingHandlerMapping.java
  8. 49
      spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/annotation/RequestParamArgumentResolver.java
  9. 99
      spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/annotation/ResponseBodyResultHandler.java
  10. 4
      spring-web-reactive/src/main/java/org/springframework/reactive/web/http/rxnetty/RxNettyServerHttpResponse.java
  11. 9
      spring-web-reactive/src/main/java/org/springframework/reactive/web/http/servlet/HttpHandlerServlet.java
  12. 8
      spring-web-reactive/src/main/resources/log4j.properties
  13. 5
      spring-web-reactive/src/test/java/org/springframework/reactive/util/BlockingByteBufQueuePublisherTests.java
  14. 4
      spring-web-reactive/src/test/java/org/springframework/reactive/util/BlockingByteBufQueueTests.java
  15. 118
      spring-web-reactive/src/test/java/org/springframework/reactive/web/dispatch/DispatcherApp.java
  16. 17
      spring-web-reactive/src/test/java/org/springframework/reactive/web/dispatch/handler/SimpleUrlHandlerMappingIntegrationTests.java
  17. 87
      spring-web-reactive/src/test/java/org/springframework/reactive/web/dispatch/method/annotation/RequestMappingIntegrationTests.java

6
spring-web-reactive/build.gradle

@ -23,8 +23,7 @@ dependencies { @@ -23,8 +23,7 @@ dependencies {
compile "org.springframework:spring-web:4.2.0.RELEASE"
compile "org.reactivestreams:reactive-streams:1.0.0"
compile "io.projectreactor:reactor-stream:2.0.5.RELEASE"
compile "org.slf4j:slf4j-api:1.7.6"
compile "ch.qos.logback:logback-classic:1.1.2"
compile "commons-logging:commons-logging:1.2"
optional "io.reactivex:rxnetty:0.5.0-SNAPSHOT"
optional "io.reactivex:rxjava-reactive-streams:1.0.1"
@ -40,7 +39,10 @@ dependencies { @@ -40,7 +39,10 @@ dependencies {
testCompile 'org.eclipse.jetty:jetty-server:9.3.2.v20150730'
testCompile 'org.eclipse.jetty:jetty-servlet:9.3.2.v20150730'
testCompile "org.slf4j:slf4j-jcl:1.7.12"
testCompile "org.slf4j:jul-to-slf4j:1.7.12"
testCompile("log4j:log4j:1.2.16")
testCompile("org.mockito:mockito-core:1.10.19") {
exclude group: 'org.hamcrest', module: 'hamcrest-core'
}

43
spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/DispatcherHandler.java

@ -17,12 +17,18 @@ package org.springframework.reactive.web.dispatch; @@ -17,12 +17,18 @@ package org.springframework.reactive.web.dispatch;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import reactor.rx.Streams;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.http.HttpStatus;
import org.springframework.reactive.web.http.HttpHandler;
import org.springframework.reactive.web.http.ServerHttpRequest;
@ -31,7 +37,10 @@ import org.springframework.reactive.web.http.ServerHttpResponse; @@ -31,7 +37,10 @@ import org.springframework.reactive.web.http.ServerHttpResponse;
/**
* @author Rossen Stoyanchev
*/
public class DispatcherHandler implements HttpHandler {
public class DispatcherHandler implements HttpHandler, ApplicationContextAware {
private static final Log logger = LogFactory.getLog(DispatcherHandler.class);
private List<HandlerMapping> handlerMappings;
@ -40,22 +49,40 @@ public class DispatcherHandler implements HttpHandler { @@ -40,22 +49,40 @@ public class DispatcherHandler implements HttpHandler {
private List<HandlerResultHandler> resultHandlers;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
initStrategies(applicationContext);
}
protected void initStrategies(ApplicationContext context) {
this.handlerMappings = new ArrayList<>(BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerMapping.class, true, false).values());
Map<String, HandlerMapping> mappingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
this.handlerAdapters = new ArrayList<>(BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerAdapter.class, true, false).values());
this.handlerMappings = new ArrayList<>(mappingBeans.values());
AnnotationAwareOrderComparator.sort(this.handlerMappings);
this.resultHandlers = new ArrayList<>(BeanFactoryUtils.beansOfTypeIncludingAncestors(
context, HandlerResultHandler.class, true, false).values());
Map<String, HandlerAdapter> adapterBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
this.handlerAdapters = new ArrayList<>(adapterBeans.values());
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
Map<String, HandlerResultHandler> beans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerResultHandler.class, true, false);
this.resultHandlers = new ArrayList<>(beans.values());
AnnotationAwareOrderComparator.sort(this.resultHandlers);
}
@Override
public Publisher<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
if (logger.isDebugEnabled()) {
logger.debug("Processing " + request.getMethod() + " request for [" + request.getURI() + "]");
}
Object handler = getHandler(request);
if (handler == null) {
// No exception handling mechanism yet
@ -73,7 +100,7 @@ public class DispatcherHandler implements HttpHandler { @@ -73,7 +100,7 @@ public class DispatcherHandler implements HttpHandler {
}
}
return Streams.fail(new IllegalStateException(
"No HandlerResultHandler for " + result.getReturnValue()));
"No HandlerResultHandler for " + result.getValue()));
});
}

18
spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/HandlerResult.java

@ -15,21 +15,29 @@ @@ -15,21 +15,29 @@
*/
package org.springframework.reactive.web.dispatch;
/**
* @author Rossen Stoyanchev
*/
public class HandlerResult {
private final Object returnValue;
private final Object handler;
private final Object value;
public HandlerResult(Object returnValue) {
this.returnValue = returnValue;
public HandlerResult(Object handler, Object value) {
this.handler = handler;
this.value = value;
}
public Object getReturnValue() {
return this.returnValue;
public Object getHandler() {
return this.handler;
}
public Object getValue() {
return this.value;
}
}

32
spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/HandlerMethodArgumentResolver.java

@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
/*
* Copyright 2002-2015 the original author or 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 org.springframework.reactive.web.dispatch.method;
import org.springframework.core.MethodParameter;
import org.springframework.reactive.web.http.ServerHttpRequest;
/**
* @author Rossen Stoyanchev
*/
public interface HandlerMethodArgumentResolver {
boolean supportsParameter(MethodParameter parameter);
Object resolveArgument(MethodParameter parameter, ServerHttpRequest request);
}

184
spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/InvocableHandlerMethod.java

@ -0,0 +1,184 @@ @@ -0,0 +1,184 @@
/*
* Copyright 2002-2015 the original author or 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 org.springframework.reactive.web.dispatch.method;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.reactive.web.http.ServerHttpRequest;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.method.HandlerMethod;
/**
* 90% overlap with the existing one in spring-web except for the different
* HandlerMethodArgumentResolver contract.
*
* @author Rossen Stoyanchev
*/
public class InvocableHandlerMethod extends HandlerMethod {
private List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
public InvocableHandlerMethod(HandlerMethod handlerMethod) {
super(handlerMethod);
}
public void setHandlerMethodArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
this.argumentResolvers.clear();
this.argumentResolvers.addAll(resolvers);
}
public Object invokeForRequest(ServerHttpRequest request, Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Invoking [" + getBeanType().getSimpleName() + "." +
getMethod().getName() + "] method with arguments " + Arrays.asList(args));
}
Object returnValue = doInvoke(args);
if (logger.isTraceEnabled()) {
logger.trace("Method [" + getMethod().getName() + "] returned [" + returnValue + "]");
}
return returnValue;
}
private Object[] getMethodArgumentValues(ServerHttpRequest request, Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
args[i] = resolveProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
try {
args[i] = resolver.resolveArgument(parameter, request);
break;
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug(getArgumentResolutionErrorMessage("Error resolving argument", i), ex);
}
throw ex;
}
}
}
if (args[i] == null) {
String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
throw new IllegalStateException(msg);
}
}
return args;
}
private String getArgumentResolutionErrorMessage(String message, int index) {
MethodParameter param = getMethodParameters()[index];
message += " [" + index + "] [type=" + param.getParameterType().getName() + "]";
return getDetailedErrorMessage(message);
}
protected String getDetailedErrorMessage(String message) {
return message + "\n" + "HandlerMethod details: \n" +
"Controller [" + getBeanType().getName() + "]\n" +
"Method [" + getBridgedMethod().toGenericString() + "]\n";
}
private Object resolveProvidedArgument(MethodParameter parameter, Object... providedArgs) {
if (providedArgs == null) {
return null;
}
for (Object providedArg : providedArgs) {
if (parameter.getParameterType().isInstance(providedArg)) {
return providedArg;
}
}
return null;
}
protected Object doInvoke(Object... args) throws Exception {
ReflectionUtils.makeAccessible(getBridgedMethod());
try {
return getBridgedMethod().invoke(getBean(), args);
}
catch (IllegalArgumentException ex) {
assertTargetBean(getBridgedMethod(), getBean(), args);
throw new IllegalStateException(getInvocationErrorMessage(ex.getMessage(), args), ex);
}
catch (InvocationTargetException ex) {
// Unwrap for HandlerExceptionResolvers ...
Throwable targetException = ex.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
}
else if (targetException instanceof Error) {
throw (Error) targetException;
}
else if (targetException instanceof Exception) {
throw (Exception) targetException;
}
else {
String msg = getInvocationErrorMessage("Failed to invoke controller method", args);
throw new IllegalStateException(msg, targetException);
}
}
}
private void assertTargetBean(Method method, Object targetBean, Object[] args) {
Class<?> methodDeclaringClass = method.getDeclaringClass();
Class<?> targetBeanClass = targetBean.getClass();
if (!methodDeclaringClass.isAssignableFrom(targetBeanClass)) {
String msg = "The mapped controller method class '" + methodDeclaringClass.getName() +
"' is not an instance of the actual controller bean instance '" +
targetBeanClass.getName() + "'. If the controller requires proxying " +
"(e.g. due to @Transactional), please use class-based proxying.";
throw new IllegalStateException(getInvocationErrorMessage(msg, args));
}
}
private String getInvocationErrorMessage(String message, Object[] resolvedArgs) {
StringBuilder sb = new StringBuilder(getDetailedErrorMessage(message));
sb.append("Resolved arguments: \n");
for (int i=0; i < resolvedArgs.length; i++) {
sb.append("[").append(i).append("] ");
if (resolvedArgs[i] == null) {
sb.append("[null] \n");
}
else {
sb.append("[type=").append(resolvedArgs[i].getClass().getName()).append("] ");
sb.append("[value=").append(resolvedArgs[i]).append("]\n");
}
}
return sb.toString();
}
}

79
spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/annotation/RequestMappingHandlerAdapter.java

@ -0,0 +1,79 @@ @@ -0,0 +1,79 @@
/*
* Copyright 2002-2015 the original author or 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 org.springframework.reactive.web.dispatch.method.annotation;
import java.util.ArrayList;
import java.util.List;
import org.reactivestreams.Publisher;
import reactor.rx.Streams;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.reactive.web.dispatch.HandlerAdapter;
import org.springframework.reactive.web.dispatch.HandlerResult;
import org.springframework.reactive.web.dispatch.method.HandlerMethodArgumentResolver;
import org.springframework.reactive.web.dispatch.method.InvocableHandlerMethod;
import org.springframework.reactive.web.http.ServerHttpRequest;
import org.springframework.reactive.web.http.ServerHttpResponse;
import org.springframework.web.method.HandlerMethod;
/**
* @author Rossen Stoyanchev
*/
public class RequestMappingHandlerAdapter implements HandlerAdapter, InitializingBean {
private List<HandlerMethodArgumentResolver> argumentResolvers;
public void setHandlerMethodArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
this.argumentResolvers.clear();
this.argumentResolvers.addAll(resolvers);
}
@Override
public void afterPropertiesSet() throws Exception {
if (this.argumentResolvers == null) {
this.argumentResolvers = new ArrayList<>();
this.argumentResolvers.add(new RequestParamArgumentResolver());
}
}
@Override
public boolean supports(Object handler) {
return HandlerMethod.class.equals(handler.getClass());
}
@Override
public Publisher<HandlerResult> handle(ServerHttpRequest request, ServerHttpResponse response,
Object handler) {
final InvocableHandlerMethod invocable = new InvocableHandlerMethod((HandlerMethod) handler);
invocable.setHandlerMethodArgumentResolvers(this.argumentResolvers);
Object result;
try {
result = invocable.invokeForRequest(request);
}
catch (Exception ex) {
return Streams.fail(ex);
}
return Streams.just(new HandlerResult(invocable, result));
}
}

91
spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/annotation/RequestMappingHandlerMapping.java

@ -0,0 +1,91 @@ @@ -0,0 +1,91 @@
/*
* Copyright 2002-2015 the original author or 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 org.springframework.reactive.web.dispatch.method.annotation;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.reactive.web.dispatch.HandlerMapping;
import org.springframework.reactive.web.http.ServerHttpRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.HandlerMethodSelector;
/**
* @author Rossen Stoyanchev
*/
public class RequestMappingHandlerMapping implements HandlerMapping,
ApplicationContextAware, InitializingBean {
private static final Log logger = LogFactory.getLog(RequestMappingHandlerMapping.class);
private final Map<String, HandlerMethod> methodMap = new LinkedHashMap<>();
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
public void afterPropertiesSet() throws Exception {
for (Object bean : this.applicationContext.getBeansOfType(Object.class).values()) {
detectHandlerMethods(bean);
}
}
protected void detectHandlerMethods(final Object bean) {
final Class<?> beanType = bean.getClass();
if (AnnotationUtils.findAnnotation(beanType, Controller.class) != null) {
HandlerMethodSelector.selectMethods(beanType, method -> {
RequestMapping annotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);
if (annotation != null && annotation.value().length > 0) {
String path = annotation.value()[0];
HandlerMethod handlerMethod = new HandlerMethod(bean, method);
if (logger.isInfoEnabled()) {
logger.info("Mapped \"" + path + "\" onto " + handlerMethod);
}
methodMap.put(path, handlerMethod);
}
return false;
});
}
}
@Override
public Object getHandler(ServerHttpRequest request) {
String path = request.getURI().getPath();
HandlerMethod handlerMethod = this.methodMap.get(path);
if (logger.isDebugEnabled()) {
logger.debug("Mapped " + path + " to [" + handlerMethod + "]");
}
return handlerMethod;
}
}

49
spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/annotation/RequestParamArgumentResolver.java

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
/*
* Copyright 2002-2015 the original author or 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 org.springframework.reactive.web.dispatch.method.annotation;
import org.springframework.core.MethodParameter;
import org.springframework.reactive.web.dispatch.method.HandlerMethodArgumentResolver;
import org.springframework.reactive.web.http.ServerHttpRequest;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
/**
* Support {@code @RequestParam} but for query params only.
*
* @author Rossen Stoyanchev
*/
public class RequestParamArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestParam.class);
}
@Override
public Object resolveArgument(MethodParameter param, ServerHttpRequest request) {
RequestParam annotation = param.getParameterAnnotation(RequestParam.class);
String name = (annotation.value().length() != 0 ? annotation.value() : param.getParameterName());
UriComponents uriComponents = UriComponentsBuilder.fromUri(request.getURI()).build();
return uriComponents.getQueryParams().getFirst(name);
}
}

99
spring-web-reactive/src/main/java/org/springframework/reactive/web/dispatch/method/annotation/ResponseBodyResultHandler.java

@ -0,0 +1,99 @@ @@ -0,0 +1,99 @@
/*
* Copyright 2002-2015 the original author or 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 org.springframework.reactive.web.dispatch.method.annotation;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import org.reactivestreams.Publisher;
import reactor.rx.Streams;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.MediaType;
import org.springframework.reactive.web.dispatch.HandlerResult;
import org.springframework.reactive.web.dispatch.HandlerResultHandler;
import org.springframework.reactive.web.http.ServerHttpRequest;
import org.springframework.reactive.web.http.ServerHttpResponse;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.HandlerMethod;
/**
* For now a simple {@code String} or {@code Publisher<String>} to
* "text/plain;charset=UTF-8" conversion.
*
* @author Rossen Stoyanchev
*/
public class ResponseBodyResultHandler implements HandlerResultHandler, Ordered {
private static final Charset UTF_8 = Charset.forName("UTF-8");
private int order = Ordered.LOWEST_PRECEDENCE;
public void setOrder(int order) {
this.order = order;
}
@Override
public int getOrder() {
return this.order;
}
@Override
public boolean supports(HandlerResult result) {
Object handler = result.getHandler();
if (handler instanceof HandlerMethod) {
Method method = ((HandlerMethod) handler).getMethod();
return AnnotatedElementUtils.isAnnotated(method, ResponseBody.class.getName());
}
return false;
}
@Override
public Publisher<Void> handleResult(ServerHttpRequest request, ServerHttpResponse response,
HandlerResult result) {
Object value = result.getValue();
HandlerMethod handlerMethod = (HandlerMethod) result.getHandler();
MethodParameter returnType = handlerMethod.getReturnValueType(value);
if (value == null) {
return Streams.empty();
}
if (value instanceof String) {
response.getHeaders().setContentType(new MediaType("text", "plain", UTF_8));
return response.writeWith(Streams.just(((String) value).getBytes(UTF_8)));
}
else if (value instanceof Publisher) {
Class<?> type = ResolvableType.forMethodParameter(returnType).resolveGeneric(0);
if (String.class.equals(type)) {
@SuppressWarnings("unchecked")
Publisher<String> content = (Publisher<String>) value;
return response.writeWith(Streams.wrap(content).map(value1 -> value1.getBytes(UTF_8)));
}
}
return Streams.fail(new IllegalStateException("Return value type not supported: " + returnType));
}
}

4
spring-web-reactive/src/main/java/org/springframework/reactive/web/http/rxnetty/RxNettyServerHttpResponse.java

@ -65,7 +65,9 @@ public class RxNettyServerHttpResponse implements ServerHttpResponse { @@ -65,7 +65,9 @@ public class RxNettyServerHttpResponse implements ServerHttpResponse {
private void writeHeaders() {
if (!this.headersWritten) {
for (String name : this.headers.keySet()) {
this.response.setHeader(name, this.headers.get(name));
for (String value : this.headers.get(name)) {
this.response.addHeader(name, value);
}
}
}
}

9
spring-web-reactive/src/main/java/org/springframework/reactive/web/http/servlet/HttpHandlerServlet.java

@ -29,6 +29,7 @@ import org.apache.commons.logging.LogFactory; @@ -29,6 +29,7 @@ import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.springframework.http.HttpStatus;
import org.springframework.reactive.web.http.HttpHandler;
/**
@ -66,7 +67,7 @@ public class HttpHandlerServlet extends HttpServlet { @@ -66,7 +67,7 @@ public class HttpHandlerServlet extends HttpServlet {
response.getOutputStream().setWriteListener(responseSubscriber);
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response, responseSubscriber);
HandlerResultSubscriber resultSubscriber = new HandlerResultSubscriber(contextSynchronizer);
HandlerResultSubscriber resultSubscriber = new HandlerResultSubscriber(contextSynchronizer, httpResponse);
this.handler.handle(httpRequest, httpResponse).subscribe(resultSubscriber);
}
@ -75,9 +76,12 @@ public class HttpHandlerServlet extends HttpServlet { @@ -75,9 +76,12 @@ public class HttpHandlerServlet extends HttpServlet {
private final AsyncContextSynchronizer synchronizer;
private final ServletServerHttpResponse response;
public HandlerResultSubscriber(AsyncContextSynchronizer synchronizer) {
public HandlerResultSubscriber(AsyncContextSynchronizer synchronizer, ServletServerHttpResponse response) {
this.synchronizer = synchronizer;
this.response = response;
}
@ -94,6 +98,7 @@ public class HttpHandlerServlet extends HttpServlet { @@ -94,6 +98,7 @@ public class HttpHandlerServlet extends HttpServlet {
@Override
public void onError(Throwable ex) {
logger.error("Error from request handling. Completing the request.", ex);
this.response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
this.synchronizer.complete();
}

8
spring-web-reactive/src/main/resources/log4j.properties

@ -1,6 +1,8 @@ @@ -1,6 +1,8 @@
log4j.rootCategory=INFO, stdout
log4j.logger.org.springframework.rx=DEBUG
log4j.rootCategory=WARN, stdout
log4j.logger.org.springframework.reactive=DEBUG
log4j.logger.org.springframework.web=DEBUG
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%25.25c{1}] <%t> - %m%n
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] <%t> - %m%n

5
spring-web-reactive/src/test/java/org/springframework/reactive/util/BlockingByteBufQueuePublisherTests.java

@ -26,7 +26,10 @@ import org.reactivestreams.Publisher; @@ -26,7 +26,10 @@ import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* @author Arjen Poutsma

4
spring-web-reactive/src/test/java/org/springframework/reactive/util/BlockingByteBufQueueTests.java

@ -19,7 +19,9 @@ package org.springframework.reactive.util; @@ -19,7 +19,9 @@ package org.springframework.reactive.util;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
/**
* @author Arjen Poutsma

118
spring-web-reactive/src/test/java/org/springframework/reactive/web/dispatch/DispatcherApp.java

@ -1,118 +0,0 @@ @@ -1,118 +0,0 @@
/*
* Copyright 2002-2015 the original author or 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 org.springframework.reactive.web.dispatch;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import io.netty.buffer.ByteBuf;
import io.reactivex.netty.protocol.http.server.HttpServer;
import org.reactivestreams.Publisher;
import reactor.rx.Streams;
import org.springframework.http.MediaType;
import org.springframework.reactive.web.http.ServerHttpRequest;
import org.springframework.reactive.web.http.ServerHttpResponse;
import org.springframework.reactive.web.http.rxnetty.RequestHandlerAdapter;
import org.springframework.web.context.support.StaticWebApplicationContext;
/**
* @author Rossen Stoyanchev
*/
public class DispatcherApp {
public static void main(String[] args) {
StaticWebApplicationContext wac = new StaticWebApplicationContext();
wac.registerSingleton("handlerMapping", SimpleUrlHandlerMapping.class);
wac.registerSingleton("handlerAdapter", PlainTextHandlerAdapter.class);
wac.registerSingleton("resultHandler", PlainTextResultHandler.class);
wac.refresh();
SimpleUrlHandlerMapping handlerMapping = wac.getBean(SimpleUrlHandlerMapping.class);
handlerMapping.addHandler("/text", new HelloWorldTextHandler());
DispatcherHandler dispatcherHandler = new DispatcherHandler();
dispatcherHandler.initStrategies(wac);
RequestHandlerAdapter requestHandler = new RequestHandlerAdapter(dispatcherHandler);
HttpServer<ByteBuf, ByteBuf> server = HttpServer.newServer(8080);
server.start(requestHandler::handle);
server.awaitShutdown();
}
private static class SimpleUrlHandlerMapping implements HandlerMapping {
private final Map<String, Object> handlerMap = new HashMap<>();
public void addHandler(String path, Object handler) {
this.handlerMap.put(path, handler);
}
@Override
public Object getHandler(ServerHttpRequest request) {
return this.handlerMap.get(request.getURI().getPath());
}
}
private interface PlainTextHandler {
Publisher<String> handle(ServerHttpRequest request, ServerHttpResponse response);
}
private static class HelloWorldTextHandler implements PlainTextHandler {
@Override
public Publisher<String> handle(ServerHttpRequest request, ServerHttpResponse response) {
return Streams.just("Hello world.");
}
}
private static class PlainTextHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
return PlainTextHandler.class.isAssignableFrom(handler.getClass());
}
@Override
public Publisher<HandlerResult> handle(ServerHttpRequest request, ServerHttpResponse response, Object handler) {
Publisher<String> publisher = ((PlainTextHandler) handler).handle(request, response);
return Streams.wrap(publisher).map(HandlerResult::new);
}
}
private static class PlainTextResultHandler implements HandlerResultHandler {
@Override
public boolean supports(HandlerResult result) {
Object value = result.getReturnValue();
return (value != null && String.class.equals(value.getClass()));
}
@Override
public Publisher<Void> handleResult(ServerHttpRequest request, ServerHttpResponse response, HandlerResult result) {
response.getHeaders().setContentType(MediaType.TEXT_PLAIN);
byte[] bytes = ((String) result.getReturnValue()).getBytes(Charset.forName("UTF-8"));
return response.writeWith(Streams.just(bytes));
}
}
}

17
spring-web-reactive/src/test/java/org/springframework/reactive/web/dispatch/SimpleUrlHandlerMappingIntegrationTests.java → spring-web-reactive/src/test/java/org/springframework/reactive/web/dispatch/handler/SimpleUrlHandlerMappingIntegrationTests.java

@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.reactive.web.dispatch;
package org.springframework.reactive.web.dispatch.handler;
import java.net.URI;
import java.nio.charset.Charset;
@ -26,8 +26,7 @@ import reactor.rx.Streams; @@ -26,8 +26,7 @@ import reactor.rx.Streams;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.reactive.web.dispatch.handler.HttpHandlerAdapter;
import org.springframework.reactive.web.dispatch.handler.SimpleUrlHandlerMapping;
import org.springframework.reactive.web.dispatch.DispatcherHandler;
import org.springframework.reactive.web.http.AbstractHttpHandlerIntegrationTests;
import org.springframework.reactive.web.http.HttpHandler;
import org.springframework.reactive.web.http.ServerHttpRequest;
@ -43,7 +42,7 @@ import static org.junit.Assert.assertArrayEquals; @@ -43,7 +42,7 @@ import static org.junit.Assert.assertArrayEquals;
*/
public class SimpleUrlHandlerMappingIntegrationTests extends AbstractHttpHandlerIntegrationTests {
private static final Charset CHARSET = Charset.forName("UTF-8");
private static final Charset UTF_8 = Charset.forName("UTF-8");
@Override
@ -55,7 +54,7 @@ public class SimpleUrlHandlerMappingIntegrationTests extends AbstractHttpHandler @@ -55,7 +54,7 @@ public class SimpleUrlHandlerMappingIntegrationTests extends AbstractHttpHandler
wac.refresh();
DispatcherHandler dispatcherHandler = new DispatcherHandler();
dispatcherHandler.initStrategies(wac);
dispatcherHandler.setApplicationContext(wac);
return dispatcherHandler;
}
@ -68,7 +67,7 @@ public class SimpleUrlHandlerMappingIntegrationTests extends AbstractHttpHandler @@ -68,7 +67,7 @@ public class SimpleUrlHandlerMappingIntegrationTests extends AbstractHttpHandler
RequestEntity<Void> request = RequestEntity.get(url).build();
ResponseEntity<byte[]> response = restTemplate.exchange(request, byte[].class);
assertArrayEquals("foo".getBytes(CHARSET), response.getBody());
assertArrayEquals("foo".getBytes(UTF_8), response.getBody());
}
@Test
@ -80,7 +79,7 @@ public class SimpleUrlHandlerMappingIntegrationTests extends AbstractHttpHandler @@ -80,7 +79,7 @@ public class SimpleUrlHandlerMappingIntegrationTests extends AbstractHttpHandler
RequestEntity<Void> request = RequestEntity.get(url).build();
ResponseEntity<byte[]> response = restTemplate.exchange(request, byte[].class);
assertArrayEquals("bar".getBytes(CHARSET), response.getBody());
assertArrayEquals("bar".getBytes(UTF_8), response.getBody());
}
@ -98,7 +97,7 @@ public class SimpleUrlHandlerMappingIntegrationTests extends AbstractHttpHandler @@ -98,7 +97,7 @@ public class SimpleUrlHandlerMappingIntegrationTests extends AbstractHttpHandler
@Override
public Publisher<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
return response.writeWith(Streams.just("foo".getBytes(CHARSET)));
return response.writeWith(Streams.just("foo".getBytes(UTF_8)));
}
}
@ -106,7 +105,7 @@ public class SimpleUrlHandlerMappingIntegrationTests extends AbstractHttpHandler @@ -106,7 +105,7 @@ public class SimpleUrlHandlerMappingIntegrationTests extends AbstractHttpHandler
@Override
public Publisher<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
return response.writeWith(Streams.just("bar".getBytes(CHARSET)));
return response.writeWith(Streams.just("bar".getBytes(UTF_8)));
}
}

87
spring-web-reactive/src/test/java/org/springframework/reactive/web/dispatch/method/annotation/RequestMappingIntegrationTests.java

@ -0,0 +1,87 @@ @@ -0,0 +1,87 @@
/*
* Copyright 2002-2015 the original author or 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 org.springframework.reactive.web.dispatch.method.annotation;
import java.net.URI;
import java.nio.charset.Charset;
import org.junit.Test;
import org.reactivestreams.Publisher;
import reactor.rx.Streams;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.reactive.web.dispatch.DispatcherHandler;
import org.springframework.reactive.web.http.AbstractHttpHandlerIntegrationTests;
import org.springframework.reactive.web.http.HttpHandler;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.support.StaticWebApplicationContext;
import static org.junit.Assert.assertArrayEquals;
/**
* @author Rossen Stoyanchev
*/
public class RequestMappingIntegrationTests extends AbstractHttpHandlerIntegrationTests {
private static final Charset UTF_8 = Charset.forName("UTF-8");
@Override
protected HttpHandler createHttpHandler() {
StaticWebApplicationContext wac = new StaticWebApplicationContext();
wac.registerSingleton("handlerMapping", RequestMappingHandlerMapping.class);
wac.registerSingleton("handlerAdapter", RequestMappingHandlerAdapter.class);
wac.registerSingleton("responseBodyResultHandler", ResponseBodyResultHandler.class);
wac.registerSingleton("controller", TestController.class);
wac.refresh();
DispatcherHandler dispatcherHandler = new DispatcherHandler();
dispatcherHandler.setApplicationContext(wac);
return dispatcherHandler;
}
@Test
public void helloWithQueryParam() throws Exception {
RestTemplate restTemplate = new RestTemplate();
URI url = new URI("http://localhost:" + port + "/param?name=George");
RequestEntity<Void> request = RequestEntity.get(url).build();
ResponseEntity<byte[]> response = restTemplate.exchange(request, byte[].class);
assertArrayEquals("Hello George!".getBytes(UTF_8), response.getBody());
}
@Controller
@SuppressWarnings("unused")
private static class TestController {
@RequestMapping("/param")
@ResponseBody
public Publisher<String> handleWithParam(@RequestParam String name) {
return Streams.just("Hello ", name, "!");
}
}
}
Loading…
Cancel
Save