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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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