Browse Source
This commit introduces HTTP request and response abstractions along with Servlet-based implementations similar to the ones in the http package of spring-web but using Reactive Streams. In turn HttpHandler now accepts the request and response types and returns Publisher<Void> that reflects the end of handling. The write method on the response also returns Publisher<Void> allowing deferred writing. At the moment however the underlying Servlet 3.1 support only supports a single publisher after which the connection is closed. Only simple byte[] is supported for reading and writing.pull/1111/head
15 changed files with 394 additions and 17 deletions
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
/* |
||||
* 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; |
||||
|
||||
import java.net.URI; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpMethod; |
||||
|
||||
/** |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public interface HttpMessage { |
||||
|
||||
HttpHeaders getHeaders(); |
||||
|
||||
} |
||||
@ -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; |
||||
|
||||
import java.net.URI; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpMethod; |
||||
|
||||
/** |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public interface HttpRequest extends HttpMessage { |
||||
|
||||
HttpMethod getMethod(); |
||||
|
||||
URI getURI(); |
||||
|
||||
} |
||||
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
/* |
||||
* 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; |
||||
|
||||
import org.reactivestreams.Publisher; |
||||
|
||||
/** |
||||
* |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public interface ServerHttpRequest extends HttpRequest { |
||||
|
||||
Publisher<byte[]> getBody(); |
||||
|
||||
} |
||||
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
/* |
||||
* 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; |
||||
|
||||
import org.reactivestreams.Publisher; |
||||
|
||||
import org.springframework.http.HttpStatus; |
||||
|
||||
/** |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public interface ServerHttpResponse extends HttpMessage { |
||||
|
||||
void setStatusCode(HttpStatus status); |
||||
|
||||
Publisher<Void> writeWith(Publisher<byte[]> contentPublisher); |
||||
|
||||
} |
||||
@ -0,0 +1,119 @@
@@ -0,0 +1,119 @@
|
||||
/* |
||||
* 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.servlet; |
||||
|
||||
import java.net.URI; |
||||
import java.net.URISyntaxException; |
||||
import java.nio.charset.Charset; |
||||
import java.util.Enumeration; |
||||
import java.util.Map; |
||||
|
||||
import javax.servlet.http.HttpServletRequest; |
||||
|
||||
import org.reactivestreams.Publisher; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.reactive.web.ServerHttpRequest; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.LinkedCaseInsensitiveMap; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public class ServletServerHttpRequest implements ServerHttpRequest { |
||||
|
||||
private final HttpServletRequest servletRequest; |
||||
|
||||
private final Publisher<byte[]> requestBodyPublisher; |
||||
|
||||
private HttpHeaders headers; |
||||
|
||||
|
||||
public ServletServerHttpRequest(HttpServletRequest servletRequest, Publisher<byte[]> requestBodyPublisher) { |
||||
Assert.notNull(servletRequest, "HttpServletRequest must not be null"); |
||||
this.servletRequest = servletRequest; |
||||
this.requestBodyPublisher = requestBodyPublisher; |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public HttpMethod getMethod() { |
||||
return HttpMethod.valueOf(this.servletRequest.getMethod()); |
||||
} |
||||
|
||||
@Override |
||||
public URI getURI() { |
||||
try { |
||||
return new URI(this.servletRequest.getScheme(), null, this.servletRequest.getServerName(), |
||||
this.servletRequest.getServerPort(), this.servletRequest.getRequestURI(), |
||||
this.servletRequest.getQueryString(), null); |
||||
} |
||||
catch (URISyntaxException ex) { |
||||
throw new IllegalStateException("Could not get HttpServletRequest URI: " + ex.getMessage(), ex); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public HttpHeaders getHeaders() { |
||||
if (this.headers == null) { |
||||
this.headers = new HttpHeaders(); |
||||
for (Enumeration<?> headerNames = this.servletRequest.getHeaderNames(); headerNames.hasMoreElements();) { |
||||
String headerName = (String) headerNames.nextElement(); |
||||
for (Enumeration<?> headerValues = this.servletRequest.getHeaders(headerName); |
||||
headerValues.hasMoreElements();) { |
||||
String headerValue = (String) headerValues.nextElement(); |
||||
this.headers.add(headerName, headerValue); |
||||
} |
||||
} |
||||
// HttpServletRequest exposes some headers as properties: we should include those if not already present
|
||||
MediaType contentType = this.headers.getContentType(); |
||||
if (contentType == null) { |
||||
String requestContentType = this.servletRequest.getContentType(); |
||||
if (StringUtils.hasLength(requestContentType)) { |
||||
contentType = MediaType.parseMediaType(requestContentType); |
||||
this.headers.setContentType(contentType); |
||||
} |
||||
} |
||||
if (contentType != null && contentType.getCharSet() == null) { |
||||
String requestEncoding = this.servletRequest.getCharacterEncoding(); |
||||
if (StringUtils.hasLength(requestEncoding)) { |
||||
Charset charSet = Charset.forName(requestEncoding); |
||||
Map<String, String> params = new LinkedCaseInsensitiveMap<>(); |
||||
params.putAll(contentType.getParameters()); |
||||
params.put("charset", charSet.toString()); |
||||
MediaType newContentType = new MediaType(contentType.getType(), contentType.getSubtype(), params); |
||||
this.headers.setContentType(newContentType); |
||||
} |
||||
} |
||||
if (this.headers.getContentLength() == -1) { |
||||
int requestContentLength = this.servletRequest.getContentLength(); |
||||
if (requestContentLength != -1) { |
||||
this.headers.setContentLength(requestContentLength); |
||||
} |
||||
} |
||||
} |
||||
return this.headers; |
||||
} |
||||
|
||||
@Override |
||||
public Publisher<byte[]> getBody() { |
||||
return this.requestBodyPublisher; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,88 @@
@@ -0,0 +1,88 @@
|
||||
/* |
||||
* 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.servlet; |
||||
|
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
|
||||
import org.reactivestreams.Publisher; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.reactive.web.ServerHttpResponse; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* @author Rossen Stoyanchev |
||||
*/ |
||||
public class ServletServerHttpResponse implements ServerHttpResponse { |
||||
|
||||
private final HttpServletResponse servletResponse; |
||||
|
||||
private final ResponseBodySubscriber responseSubscriber; |
||||
|
||||
private final HttpHeaders headers; |
||||
|
||||
private boolean headersWritten = false; |
||||
|
||||
|
||||
public ServletServerHttpResponse(HttpServletResponse servletResponse, ResponseBodySubscriber responseSubscriber) { |
||||
Assert.notNull(servletResponse, "'servletResponse' must not be null"); |
||||
Assert.notNull(responseSubscriber, "'responseSubscriber' must not be null"); |
||||
this.servletResponse = servletResponse; |
||||
this.responseSubscriber = responseSubscriber; |
||||
this.headers = new HttpHeaders(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void setStatusCode(HttpStatus status) { |
||||
this.servletResponse.setStatus(status.value()); |
||||
} |
||||
|
||||
@Override |
||||
public HttpHeaders getHeaders() { |
||||
return (this.headersWritten ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers); |
||||
} |
||||
|
||||
@Override |
||||
public Publisher<Void> writeWith(final Publisher<byte[]> contentPublisher) { |
||||
writeHeaders(); |
||||
return (s -> contentPublisher.subscribe(responseSubscriber)); |
||||
} |
||||
|
||||
private void writeHeaders() { |
||||
if (!this.headersWritten) { |
||||
for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) { |
||||
String headerName = entry.getKey(); |
||||
for (String headerValue : entry.getValue()) { |
||||
this.servletResponse.addHeader(headerName, headerValue); |
||||
} |
||||
} |
||||
// HttpServletResponse exposes some headers as properties: we should include those if not already present
|
||||
if (this.servletResponse.getContentType() == null && this.headers.getContentType() != null) { |
||||
this.servletResponse.setContentType(this.headers.getContentType().toString()); |
||||
} |
||||
if (this.servletResponse.getCharacterEncoding() == null && this.headers.getContentType() != null && |
||||
this.headers.getContentType().getCharSet() != null) { |
||||
this.servletResponse.setCharacterEncoding(this.headers.getContentType().getCharSet().name()); |
||||
} |
||||
this.headersWritten = true; |
||||
} |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue