introduce builders for server requests und responses, add context info on server requests

This commit is contained in:
Jörg Prante 2020-07-27 10:43:57 +02:00
parent fb062ebf04
commit 849a77aeec
59 changed files with 1573 additions and 1223 deletions

View file

@ -1,6 +1,6 @@
group = org.xbib
name = netty-http
version = 4.1.51.2
version = 4.1.51.3
gradle.wrapper.version = 6.4.1
netty.version = 4.1.51.Final

View file

@ -1,4 +1,4 @@
package org.xbib.netty.http.server.util;
package org.xbib.netty.http.common.util;
public class HtmlUtils {

View file

@ -18,7 +18,5 @@ public interface Domain<R extends EndpointResolver<?>> {
Collection<? extends X509Certificate> getCertificateChain();
String findContextPathOf(ServerRequest serverRequest) throws IOException;
void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException;
void handle(ServerRequest.Builder serverRequestBuilder, ServerResponse.Builder serverResponseBuilder) throws IOException;
}

View file

@ -10,7 +10,9 @@ public interface Endpoint<D extends EndpointDescriptor> {
boolean matches(D descriptor);
void resolveUriTemplate(ServerRequest serverRequest) throws IOException;
ServerRequest resolveRequest(ServerRequest.Builder serverRequestBuilder,
Domain<? extends EndpointResolver<? extends Endpoint<?>>> domain,
EndpointResolver<? extends Endpoint<?>> endpointResolver);
void before(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException;

View file

@ -1,9 +0,0 @@
package org.xbib.netty.http.server.api;
import java.io.IOException;
@FunctionalInterface
public interface EndpointDispatcher<E extends Endpoint<?>> {
void dispatch(E endpoint, ServerRequest serverRequest, ServerResponse serverResponse) throws IOException;
}

View file

@ -1,16 +1,14 @@
package org.xbib.netty.http.server.api;
import org.xbib.netty.http.common.HttpMethod;
import java.io.IOException;
import java.util.List;
public interface EndpointResolver<E extends Endpoint<? extends EndpointDescriptor>> {
public interface EndpointResolver<E extends Endpoint<?>> {
List<E> matchingEndpointsFor(ServerRequest serverRequest);
List<E> matchingEndpointsFor(String path, HttpMethod method, String contentType);
void resolve(List<E> matchingEndpoints,
ServerRequest serverRequest) throws IOException;
void handle(List<E> matchingEndpoints,
void handle(E matchingEndpoint,
ServerRequest serverRequest,
ServerResponse serverResponse) throws IOException;
}

View file

@ -6,23 +6,23 @@ import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import org.xbib.net.URL;
import org.xbib.netty.http.common.HttpParameters;
import javax.net.ssl.SSLSession;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Map;
public interface ServerRequest {
Builder getBuilder();
InetSocketAddress getLocalAddress();
InetSocketAddress getRemoteAddress();
URL getURL();
void setContext(List<String> context);
List<String> getContext();
void addPathParameter(String key, String value) throws IOException;
Map<String, String> getPathParameters();
String getRequestURI();
@ -49,7 +49,40 @@ public interface ServerRequest {
SSLSession getSession();
InetSocketAddress getLocalAddress();
URL getBaseURL();
InetSocketAddress getRemoteAddress();
URL getContextURL();
Domain<? extends EndpointResolver<? extends Endpoint<?>>> getDomain();
EndpointResolver<? extends Endpoint<?>> getEndpointResolver();
Endpoint<?> getEndpoint();
interface Builder {
String getRequestURI();
HttpMethod getMethod();
HttpHeaders getHeaders();
String getEffectiveRequestPath();
Builder setBaseURL(URL baseURL);
Builder setDomain(Domain<? extends EndpointResolver<? extends Endpoint<?>>> domain);
Builder setEndpointResolver(EndpointResolver<? extends Endpoint<?>> endpointResolver);
Builder setEndpoint(Endpoint<?> endpoint);
Builder setContext(List<String> context);
Builder addPathParameter(String key, String value);
ServerRequest build();
void release();
}
}

View file

@ -2,38 +2,34 @@ package org.xbib.netty.http.server.api;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.stream.ChunkedInput;
import org.xbib.netty.http.common.cookie.Cookie;
import java.io.Flushable;
import java.io.IOException;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/**
* HTTP server response.
*/
public interface ServerResponse {
public interface ServerResponse extends Flushable {
ChannelHandlerContext getChannelHandlerContext();
Builder getBuilder();
HttpResponseStatus getStatus();
Integer getStreamId();
ServerResponse withStatus(HttpResponseStatus httpResponseStatus);
Integer getSequenceId();
ServerResponse withHeader(CharSequence name, String value);
ServerResponse withContentType(String contentType);
ServerResponse withCharset(Charset charset);
ServerResponse withCookie(Cookie cookie);
Long getResponseId();
ByteBufOutputStream getOutputStream();
void flush();
void flush() throws IOException;
void write(String content);
void write(CharBuffer charBuffer, Charset charset);
void write(byte[] bytes);
@ -43,56 +39,30 @@ public interface ServerResponse {
void write(ChunkedInput<ByteBuf> chunkedInput);
static void write(ServerResponse serverResponse, int status) {
write(serverResponse, HttpResponseStatus.valueOf(status));
}
interface Builder {
static void write(ServerResponse serverResponse, HttpResponseStatus status) {
write(serverResponse, status, "application/octet-stream", EMPTY_STRING);
}
Builder setStatus(HttpResponseStatus httpResponseStatus);
/**
* Responses to a HEAD request.
* @param serverResponse server response
* @param status status
* @param contentType content-type as if it were for a GET request (RFC 2616)
*/
static void write(ServerResponse serverResponse, HttpResponseStatus status, String contentType) {
write(serverResponse, status, contentType, EMPTY_STRING);
}
Builder setContentType(CharSequence contentType);
static void write(ServerResponse serverResponse, String text) {
write(serverResponse, HttpResponseStatus.OK, "text/plain", text);
}
Builder setCharset(Charset charset);
static void write(ServerResponse serverResponse, HttpResponseStatus status, String contentType, String text) {
ByteBuf byteBuf = ByteBufUtil.writeUtf8(serverResponse.getChannelHandlerContext().alloc(), text);
serverResponse.withStatus(status)
.withContentType(contentType)
.withCharset(StandardCharsets.UTF_8)
.write(byteBuf);
}
Builder setHeader(CharSequence name, String value);
static void write(ServerResponse serverResponse, HttpResponseStatus status, String contentType, ByteBuf byteBuf) {
serverResponse.withStatus(status)
.withContentType(contentType)
.withCharset(StandardCharsets.UTF_8)
.write(byteBuf);
}
Builder setTrailingHeader(CharSequence name, String value);
static void write(ServerResponse serverResponse,
HttpResponseStatus status, String contentType, String text, Charset charset) {
write(serverResponse, status, contentType, CharBuffer.allocate(text.length()).append(text), charset);
}
Builder addCookie(Cookie cookie);
static void write(ServerResponse serverResponse, HttpResponseStatus status, String contentType,
CharBuffer charBuffer, Charset charset) {
ByteBuf byteBuf = ByteBufUtil.encodeString(serverResponse.getChannelHandlerContext().alloc(), charBuffer, charset);
serverResponse.withStatus(status)
.withContentType(contentType)
.withCharset(charset)
.write(byteBuf);
}
Builder shouldClose(boolean shouldClose);
String EMPTY_STRING = "";
Builder shouldAddServerName(boolean shouldAddServerName);
Builder setSequenceId(Integer sequenceId);
Builder setStreamId(Integer streamId);
Builder setResponseId(Long responseId);
ServerResponse build();
}
}

View file

@ -1,5 +1,5 @@
import org.xbib.netty.http.server.Http1;
import org.xbib.netty.http.server.Http2;
import org.xbib.netty.http.server.protocol.http1.Http1;
import org.xbib.netty.http.server.protocol.http2.Http2;
module org.xbib.netty.http.server {
uses org.xbib.netty.http.server.api.security.ServerCertificateProvider;
@ -10,10 +10,8 @@ module org.xbib.netty.http.server {
exports org.xbib.netty.http.server.endpoint;
exports org.xbib.netty.http.server.endpoint.service;
exports org.xbib.netty.http.server.handler;
exports org.xbib.netty.http.server.handler.http;
exports org.xbib.netty.http.server.handler.http2;
exports org.xbib.netty.http.server.handler.stream;
exports org.xbib.netty.http.server.transport;
exports org.xbib.netty.http.server.protocol.http1;
exports org.xbib.netty.http.server.protocol.http2;
exports org.xbib.netty.http.server.util;
requires transitive org.xbib.netty.http.server.api;
requires java.logging;

View file

@ -0,0 +1,23 @@
package org.xbib.netty.http.server;
import io.netty.handler.codec.http.HttpResponseStatus;
public enum AcceptState {
OK(HttpResponseStatus.OK, null, null),
MISSING_HOST_HEADER(HttpResponseStatus.BAD_REQUEST, "application/octet-stream", "missing 'Host' header"),
EXPECTATION_FAILED(HttpResponseStatus.EXPECTATION_FAILED, null, null),
UNSUPPORTED_HTTP_VERSION( HttpResponseStatus.BAD_REQUEST, "application/octet-stream", "unsupported HTTP version");
HttpResponseStatus status;
String contentType;
String content;
AcceptState(HttpResponseStatus status, String contentType, String content) {
this.status = status;
this.contentType = contentType;
this.content = content;
}
}

View file

@ -0,0 +1,57 @@
package org.xbib.netty.http.server;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpVersion;
import org.xbib.netty.http.server.api.ServerTransport;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
public abstract class BaseTransport implements ServerTransport {
private static final Logger logger = Logger.getLogger(BaseTransport.class.getName());
protected final Server server;
protected BaseTransport(Server server) {
this.server = server;
}
@Override
public void exceptionReceived(ChannelHandlerContext ctx, Throwable throwable) {
logger.log(Level.WARNING, throwable.getMessage(), throwable);
}
/**
* Accepts a request, performing various validation checks
* and required special header handling, possibly returning an
* appropriate response.
*
* @param httpVersion the server HTTP version
* @param reqHeaders the request headers
* @return whether further processing should be performed
*/
protected static AcceptState acceptRequest(HttpVersion httpVersion,
HttpHeaders reqHeaders) {
if (httpVersion.majorVersion() == 1 || httpVersion.majorVersion() == 2) {
if (!reqHeaders.contains(HttpHeaderNames.HOST)) {
// RFC2616#14.23: missing Host header gets 400
return AcceptState.MISSING_HOST_HEADER;
}
// return a continue response before reading body
String expect = reqHeaders.get(HttpHeaderNames.EXPECT);
if (expect != null) {
if (!"100-continue".equalsIgnoreCase(expect)) {
// RFC2616#14.20: if unknown expect, send 417
return AcceptState.EXPECTATION_FAILED;
}
}
return AcceptState.OK;
} else {
return AcceptState.UNSUPPORTED_HTTP_VERSION;
}
}
}

View file

@ -45,19 +45,19 @@ public class DefaultServerConfig implements ServerConfig {
int PARENT_THREAD_COUNT = 0;
/**
* Child thread count. Let Netty decide.
* Let Netty decide about child thread count.
*/
int CHILD_THREAD_COUNT = 0;
/**
* Blocking thread pool count.
* Blocking thread pool count. Disabled by default, use Netty threads.
*/
int BLOCKING_THREAD_COUNT = Runtime.getRuntime().availableProcessors();
int BLOCKING_THREAD_COUNT = 0;
/**
* Blocking thread pool queue count.
* Blocking thread pool queue count. Disabled by default, use Netty threads.
*/
int BLOCKING_QUEUE_COUNT = 1024;
int BLOCKING_QUEUE_COUNT = 0;
/**
* Default for SO_REUSEADDR.

View file

@ -7,7 +7,9 @@ import io.netty.handler.ssl.CipherSuiteFilter;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.common.HttpMethod;
import org.xbib.netty.http.server.api.Domain;
import org.xbib.netty.http.server.api.EndpointResolver;
import org.xbib.netty.http.server.api.security.ServerCertificateProvider;
@ -36,21 +38,14 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The {@code HttpServerDomain} class represents a virtual server with a name.
*/
public class HttpServerDomain implements Domain<HttpEndpointResolver> {
private static final Logger logger = Logger.getLogger(HttpServerDomain.class.getName());
private static final String EMPTY = "";
private final String name;
private final HttpAddress httpAddress;
@ -137,40 +132,41 @@ public class HttpServerDomain implements Domain<HttpEndpointResolver> {
}
/**
* Evaluate the context path of a given request.
* The request is not dispatched.
* URI request parameters are evaluated.
* @param serverRequest the server request
* @return the context path
* @throws IOException if handling fails
*/
@Override
public String findContextPathOf(ServerRequest serverRequest) throws IOException {
if (serverRequest == null) {
return EMPTY;
}
Map.Entry<HttpEndpointResolver, List<HttpEndpoint>> resolved = resolve(serverRequest);
if (resolved != null) {
resolved.getKey().resolve(resolved.getValue(), serverRequest);
return serverRequest.getContextPath();
}
return null;
}
/**
* Handle server requests by resolving and handling a server request.
* @param serverRequest the server request
* @param serverResponse the server response
* Handle server requests by resolving and handling.
* @param serverRequestBuilder the server request
* @param serverResponseBuilder the server response
* @throws IOException if handling server request fails
*/
@Override
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
Map.Entry<HttpEndpointResolver, List<HttpEndpoint>> resolved = resolve(serverRequest);
if (resolved != null) {
resolved.getKey().handle(resolved.getValue(), serverRequest, serverResponse);
public void handle(ServerRequest.Builder serverRequestBuilder,
ServerResponse.Builder serverResponseBuilder) throws IOException {
String path = extractPath(serverRequestBuilder.getRequestURI());
HttpMethod method = Enum.valueOf(HttpMethod.class, serverRequestBuilder.getMethod().name());
String contentType = serverRequestBuilder.getHeaders().get(CONTENT_TYPE);
HttpEndpointResolver httpEndpointResolver = null;
List<HttpEndpoint> endpoints = null;
for (HttpEndpointResolver endpointResolver : httpEndpointResolvers) {
List<HttpEndpoint> matchingEndpoints = endpointResolver.matchingEndpointsFor(path, method, contentType);
if (!matchingEndpoints.isEmpty()) {
httpEndpointResolver = endpointResolver;
endpoints = matchingEndpoints;
break;
}
}
if (endpoints != null) {
for (HttpEndpoint httpEndpoint : endpoints) {
ServerRequest resolvedServerRequest = httpEndpoint.resolveRequest(serverRequestBuilder, this, httpEndpointResolver);
if (serverResponseBuilder != null) {
httpEndpointResolver.handle(httpEndpoint, resolvedServerRequest, serverResponseBuilder.build());
}
break;
}
} else {
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND,
"text/plain", "No endpoint found to match request");
if (serverResponseBuilder != null) {
serverResponseBuilder.setStatus(HttpResponseStatus.NOT_FOUND)
.setContentType("text/plain")
.build().write("no endpoint found to match request");
}
}
}
@ -179,19 +175,13 @@ public class HttpServerDomain implements Domain<HttpEndpointResolver> {
return name + " (" + httpAddress + ")";
}
/**
* Just resolve a server request to a matching endpoint resolver with endpoints matched.
* @param serverRequest the server request
* @return the endpoint resolver together with the matching endpoints
*/
private Map.Entry<HttpEndpointResolver, List<HttpEndpoint>> resolve(ServerRequest serverRequest) {
for (HttpEndpointResolver httpEndpointResolver : httpEndpointResolvers) {
List<HttpEndpoint> matchingEndpoints = httpEndpointResolver.matchingEndpointsFor(serverRequest);
if (!matchingEndpoints.isEmpty()) {
return Map.entry(httpEndpointResolver, matchingEndpoints);
}
}
return null;
private static String extractPath(String uri) {
String path = uri;
int pos = uri.lastIndexOf('#');
path = pos >= 0 ? path.substring(0, pos) : path;
pos = uri.lastIndexOf('?');
path = pos >= 0 ? path.substring(0, pos) : path;
return path;
}
public static class Builder {
@ -323,7 +313,6 @@ public class HttpServerDomain implements Domain<HttpEndpointResolver> {
serverCertificateProvider.prepare(serverName);
setKeyCertChain(serverCertificateProvider.getCertificateChain());
setKey(serverCertificateProvider.getPrivateKey(), serverCertificateProvider.getKeyPassword());
logger.log(Level.INFO, "self signed certificate installed");
}
}
if (keyCertChain == null) {
@ -346,7 +335,7 @@ public class HttpServerDomain implements Domain<HttpEndpointResolver> {
.addEndpoint(HttpEndpoint.builder()
.setPath(path)
.build())
.setDispatcher((endpoint, req, resp) -> filter.handle(req, resp))
.setDispatcher(filter::handle)
.build());
return this;
}
@ -360,7 +349,7 @@ public class HttpServerDomain implements Domain<HttpEndpointResolver> {
.setPrefix(prefix)
.setPath(path)
.build())
.setDispatcher((endpoint, req, resp) -> filter.handle(req, resp))
.setDispatcher(filter::handle)
.build());
return this;
}
@ -376,7 +365,7 @@ public class HttpServerDomain implements Domain<HttpEndpointResolver> {
.setPath(path)
.setMethods(Arrays.asList(methods))
.build())
.setDispatcher((endpoint, req, resp) -> filter.handle(req, resp))
.setDispatcher(filter::handle)
.build());
return this;
}

View file

@ -0,0 +1,419 @@
package org.xbib.netty.http.server;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpUtil;
import org.xbib.net.Pair;
import org.xbib.net.PercentDecoder;
import org.xbib.net.QueryParameters;
import org.xbib.net.URL;
import org.xbib.netty.http.common.HttpParameters;
import org.xbib.netty.http.server.api.Domain;
import org.xbib.netty.http.server.api.Endpoint;
import org.xbib.netty.http.server.api.EndpointResolver;
import org.xbib.netty.http.server.api.ServerRequest;
import javax.net.ssl.SSLSession;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The {@code HttpServerRequest} class encapsulates a single request.
*/
public class HttpServerRequest implements ServerRequest {
private static final Logger logger = Logger.getLogger(HttpServerRequest.class.getName());
private static final String PATH_SEPARATOR = "/";
private final Builder builder;
private final InetSocketAddress localAddress;
private final InetSocketAddress remoteAddress;
private final FullHttpRequest httpRequest;
private final URL baseURL;
private final URL contextURL;
private final URL url;
private final List<String> context;
private final String contextPath;
private final HttpParameters parameters;
private final Map<String, String> pathParameters;
private final String effectiveRequestPath;
private final Integer sequenceId;
private final Integer streamId;
private final Long requestId;
private final SSLSession sslSession;
private final Domain<? extends EndpointResolver<? extends Endpoint<?>>> domain;
private final EndpointResolver<? extends Endpoint<?>> endpointResolver;
private final Endpoint<?> endpoint;
private HttpServerRequest(Builder builder) {
this.builder = builder;
this.localAddress = builder.localAddress;
this.remoteAddress = builder.remoteAddress;
this.httpRequest = builder.fullHttpRequest;
this.baseURL = builder.baseURL;
this.contextURL = builder.contextURL;
this.url = builder.url;
this.context = builder.context;
this.contextPath = builder.contextPath;
this.parameters = builder.parameters;
this.pathParameters = builder.pathParameters;
this.effectiveRequestPath = builder.effectiveRequestPath;
this.sequenceId = builder.sequenceId;
this.streamId = builder.streamId;
this.requestId = builder.requestId;
this.sslSession = builder.sslSession;
this.domain = builder.domain;
this.endpointResolver = builder.endpointResolver;
this.endpoint = builder.endpoint;
}
public Builder getBuilder() {
return builder;
}
public static Builder builder() {
return new Builder();
}
@Override
public InetSocketAddress getLocalAddress() {
return localAddress;
}
@Override
public InetSocketAddress getRemoteAddress() {
return remoteAddress;
}
@Override
public HttpMethod getMethod() {
return httpRequest.method();
}
@Override
public String getRequestURI() {
return httpRequest.uri();
}
@Override
public HttpHeaders getHeaders() {
return httpRequest.headers();
}
@Override
public ByteBuf getContent() {
return httpRequest.content();
}
@Override
public ByteBufInputStream getInputStream() {
return new ByteBufInputStream(httpRequest.content(), true);
}
@Override
public URL getBaseURL() {
return baseURL;
}
@Override
public List<String> getContext() {
return context;
}
@Override
public String getContextPath() {
return contextPath;
}
@Override
public URL getContextURL() {
return contextURL;
}
@Override
public String getEffectiveRequestPath() {
return effectiveRequestPath;
}
@Override
public Map<String, String> getPathParameters() {
return pathParameters;
}
@Override
public Domain<? extends EndpointResolver<? extends Endpoint<?>>> getDomain() {
return domain;
}
@Override
public EndpointResolver<? extends Endpoint<?>> getEndpointResolver() {
return endpointResolver;
}
@Override
public Endpoint<?> getEndpoint() {
return endpoint;
}
@Override
public URL getURL() {
return url;
}
@Override
public HttpParameters getParameters() {
return parameters;
}
@Override
public Integer getSequenceId() {
return sequenceId;
}
@Override
public Integer getStreamId() {
return streamId;
}
@Override
public Long getRequestId() {
return requestId;
}
@Override
public SSLSession getSession() {
return sslSession;
}
@Override
public String toString() {
return "ServerRequest[request=" + httpRequest + "]";
}
public static class Builder implements ServerRequest.Builder {
private final Map<String, String> pathParameters;
private InetSocketAddress localAddress;
private InetSocketAddress remoteAddress;
private FullHttpRequest fullHttpRequest;
private URL baseURL;
private URL contextURL;
private URL url;
private List<String> context;
private String contextPath;
private String effectiveRequestPath;
private HttpParameters parameters;
private Domain<? extends EndpointResolver<? extends Endpoint<?>>> domain;
private EndpointResolver<? extends Endpoint<?>> endpointResolver;
private Endpoint<?> endpoint;
private Integer sequenceId;
private Integer streamId;
private Long requestId;
private SSLSession sslSession;
private Builder() {
this.pathParameters = new LinkedHashMap<>();
}
public Builder setLocalAddress(InetSocketAddress localAddress) {
this.localAddress = localAddress;
return this;
}
public Builder setRemoteAddress(InetSocketAddress remoteAddress) {
this.remoteAddress = remoteAddress;
return this;
}
public Builder setHttpRequest(FullHttpRequest fullHttpRequest) {
this.fullHttpRequest = fullHttpRequest;
return this;
}
public String getRequestURI() {
return fullHttpRequest.uri();
}
public HttpMethod getMethod() {
return fullHttpRequest.method();
}
public HttpHeaders getHeaders() {
return fullHttpRequest.headers();
}
public Builder setBaseURL(URL baseURL) {
this.baseURL = baseURL;
return this;
}
public Builder setContext(List<String> context) {
this.context = context;
this.contextPath = context != null ? PATH_SEPARATOR + String.join(PATH_SEPARATOR, context) : null;
this.contextURL = baseURL.resolve(contextPath != null ? contextPath + "/" : "");
String path = extractPath(fullHttpRequest.uri());
String effective = contextPath != null && !PATH_SEPARATOR.equals(contextPath) && path.startsWith(contextPath) ?
path.substring(contextPath.length()) : path;
this.effectiveRequestPath = effective.isEmpty() ? PATH_SEPARATOR : effective;
return this;
}
public String getEffectiveRequestPath() {
return effectiveRequestPath;
}
public Builder addPathParameter(String key, String value) {
pathParameters.put(key, value);
//parameters.addRaw(key, value);
return this;
}
public Builder setSequenceId(Integer sequenceId) {
this.sequenceId = sequenceId;
return this;
}
public Builder setStreamId(Integer streamId) {
this.streamId = streamId;
return this;
}
public Builder setRequestId(Long requestId) {
this.requestId = requestId;
return this;
}
public Builder setSession(SSLSession sslSession) {
this.sslSession = sslSession;
return this;
}
public Builder setDomain(Domain<? extends EndpointResolver<? extends Endpoint<?>>> domain) {
this.domain = domain;
return this;
}
public Builder setEndpointResolver(EndpointResolver<? extends Endpoint<?>> endpointResolver) {
this.endpointResolver = endpointResolver;
return this;
}
public Builder setEndpoint(Endpoint<?> endpoint) {
this.endpoint = endpoint;
return this;
}
public ServerRequest build() {
// build URL and parameters
Charset charset = HttpUtil.getCharset(fullHttpRequest, StandardCharsets.UTF_8);
// creates path, query params, fragment
this.url = URL.builder()
.charset(charset, CodingErrorAction.REPLACE)
.path(fullHttpRequest.uri()) // creates path, query params, fragment
.build();
QueryParameters queryParameters = url.getQueryParams();
CharSequence mimeType = HttpUtil.getMimeType(fullHttpRequest);
ByteBuf byteBuf = fullHttpRequest.content();
if (byteBuf != null) {
if (fullHttpRequest.method().equals(HttpMethod.POST)) {
String params;
// https://www.w3.org/TR/html4/interact/forms.html#h-17.13.4
if (mimeType != null && HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString().equals(mimeType.toString())) {
Charset htmlCharset = HttpUtil.getCharset(fullHttpRequest, StandardCharsets.ISO_8859_1);
params = byteBuf.toString(htmlCharset).replace('+', ' ');
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER, "html form, charset = " + htmlCharset + " param body = " + params);
}
queryParameters.addPercentEncodedBody(params);
}
}
}
// copy to HTTP parameters but percent-decoded (looks very clumsy)
PercentDecoder percentDecoder = new PercentDecoder(charset.newDecoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE));
this.parameters = new HttpParameters(mimeType, charset);
for (Pair<String, String> pair : queryParameters) {
try {
parameters.addRaw(percentDecoder.decode(pair.getFirst()), percentDecoder.decode(pair.getSecond()));
} catch (Exception e) {
// does not happen
throw new IllegalArgumentException(pair.toString());
}
}
return new HttpServerRequest(this);
}
@Override
public void release() {
fullHttpRequest.release();
}
public ServerRequest applyTo(Server server) {
URL baseURL = server.getBaseURL(fullHttpRequest.headers());
setBaseURL(baseURL);
Domain<? extends EndpointResolver<?>> domain = server.getDomain(baseURL);
try {
domain.handle(this, null);
} catch (Throwable t) {
logger.log(Level.SEVERE, t.getMessage(), t);
}
return build();
}
private String extractPath(String uri) {
String path = uri;
int pos = uri.lastIndexOf('#');
path = pos >= 0 ? path.substring(0, pos) : path;
pos = uri.lastIndexOf('?');
path = pos >= 0 ? path.substring(0, pos) : path;
return path;
}
}
}

View file

@ -9,6 +9,7 @@ import io.netty.channel.WriteBufferWaterMark;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
@ -27,7 +28,6 @@ import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.api.ServerTransport;
import org.xbib.netty.http.server.endpoint.HttpEndpointResolver;
import org.xbib.netty.http.server.security.CertificateUtils;
import org.xbib.netty.http.server.transport.HttpServerRequest;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.security.cert.CertificateExpiredException;
@ -70,10 +70,6 @@ public final class Server implements AutoCloseable {
}
}
private static final AtomicLong requestCounter = new AtomicLong(0);
private static final AtomicLong responseCounter = new AtomicLong(0);
private final DefaultServerConfig serverConfig;
private final EventLoopGroup parentEventLoopGroup;
@ -81,7 +77,8 @@ public final class Server implements AutoCloseable {
private final EventLoopGroup childEventLoopGroup;
/**
* A thread pool for executing blocking tasks.
* An extra thread pool for handling requests. May be null
* for executing request on the Netty event pool threads.
*/
private final BlockingThreadPoolExecutor executor;
@ -91,6 +88,10 @@ public final class Server implements AutoCloseable {
private final List<ServerProtocolProvider<HttpChannelInitializer, ServerTransport>> protocolProviders;
private static final AtomicLong requestCounter = new AtomicLong();
private static final AtomicLong responseCounter = new AtomicLong();
/**
* Create a new HTTP server.
*
@ -196,14 +197,40 @@ public final class Server implements AutoCloseable {
return channelFuture;
}
/**
* Returns the domain for the given server request.
*
* @param serverRequest the server request
* @return the domain
*/
public Domain<? extends EndpointResolver<?>> getDomain(ServerRequest serverRequest) {
return getDomain(getBaseURL(serverRequest));
public AtomicLong getRequestCounter() {
return requestCounter;
}
public AtomicLong getResponseCounter() {
return responseCounter;
}
public URL getBaseURL(HttpHeaders headers) {
URL bindURL = serverConfig.getDefaultDomain().getHttpAddress().base();
String scheme = headers != null ? headers.get("x-forwarded-proto") : null;
if (scheme == null) {
scheme = bindURL.getScheme();
}
String host = headers != null ? headers.get("x-forwarded-host") : null;
if (host == null) {
host = headers != null ? headers.get("host") : null;
if (host == null) {
host = bindURL.getHost();
}
}
String port = null;
if (host != null) {
host = stripPort(host);
port = extractPort(host);
if (port == null) {
port = bindURL.getPort() != null ? Integer.toString(bindURL.getPort()) : null;
}
}
URL.Builder builder = URL.builder().scheme(scheme).host(host);
if (port != null) {
builder.port(Integer.parseInt(port));
}
return builder.build();
}
/**
@ -226,98 +253,30 @@ public final class Server implements AutoCloseable {
return serverConfig.getDomain(name);
}
/**
* Return the base URL regarding to a server request.
* The base URL depends on the host and port defined in a reqeust,
* if no request is defined, the bind URL is taken.
* @param serverRequest the server request
* @return the URL
*/
public URL getBaseURL(ServerRequest serverRequest) {
URL bindURL = serverConfig.getDefaultDomain().getHttpAddress().base();
String scheme = serverRequest != null ? serverRequest.getHeaders().get("x-forwarded-proto") : null;
if (scheme == null) {
scheme = bindURL.getScheme();
}
String host = serverRequest != null ? serverRequest.getHeaders().get("x-forwarded-host") : null;
if (host == null) {
host = serverRequest != null ? serverRequest.getHeaders().get("host") : null;
if (host == null) {
host = bindURL.getHost();
}
}
String port = null;
if (host != null) {
host = stripPort(host);
port = extractPort(host);
if (port == null) {
port = bindURL.getPort() != null ? Integer.toString(bindURL.getPort()) : null;
}
}
URL.Builder builder = URL.builder().scheme(scheme).host(host);
if (port != null) {
builder.port(Integer.parseInt(port));
}
return builder.build();
}
/**
* Return the context URL of this server. This is equivalent to the bindURL
* @return the context URL
* @throws IOException should not happen
*/
public URL getContextURL() throws IOException {
return getContextURL(null);
}
/**
* Get context URL of this server regarding to a given request.
* The context URL is the base URL with the path given in the matching
* domain prefix setting of the endpoint resolver.
* @param serverRequest the server request
* @return the context URL
* @throws IOException if context path finding fails
*/
public URL getContextURL(ServerRequest serverRequest) throws IOException {
URL baseURL = getBaseURL(serverRequest);
public void handle(ServerRequest.Builder serverRequestBuilder,
ServerResponse.Builder serverResponseBuilder) throws IOException {
URL baseURL = getBaseURL(serverRequestBuilder.getHeaders());
serverRequestBuilder.setBaseURL(baseURL);
Domain<? extends EndpointResolver<?>> domain = getDomain(baseURL);
String context = domain.findContextPathOf(serverRequest);
if (!context.endsWith("/")) {
context = context + "/";
}
return baseURL.resolve(context);
}
public void handle(HttpServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
Domain<? extends EndpointResolver<?>> domain = getDomain(serverRequest);
logger.log(Level.FINEST, () -> "found domain " + domain + " for " + serverRequest);
if (executor != null) {
executor.submit(() -> {
try {
domain.handle(serverRequest, serverResponse);
domain.handle(serverRequestBuilder, serverResponseBuilder);
} catch (IOException e) {
executor.afterExecute(null, e);
} finally {
serverRequest.release();
serverRequestBuilder.release();
}
});
} else {
try {
domain.handle(serverRequest, serverResponse);
domain.handle(serverRequestBuilder, serverResponseBuilder);
} finally {
serverRequest.release();
serverRequestBuilder.release();
}
}
}
public AtomicLong getRequestCounter() {
return requestCounter;
}
public AtomicLong getResponseCounter() {
return responseCounter;
}
public ServerTransport newTransport(HttpVersion httpVersion) {
for (ServerProtocolProvider<HttpChannelInitializer, ServerTransport> protocolProvider : protocolProviders) {
if (protocolProvider.supportsMajorVersion(httpVersion.majorVersion())) {

View file

@ -5,7 +5,9 @@ import org.xbib.net.QueryParameters;
import org.xbib.net.path.PathMatcher;
import org.xbib.net.path.PathNormalizer;
import org.xbib.netty.http.common.HttpMethod;
import org.xbib.netty.http.server.api.Domain;
import org.xbib.netty.http.server.api.Endpoint;
import org.xbib.netty.http.server.api.EndpointResolver;
import org.xbib.netty.http.server.api.ServerRequest;
import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.api.Filter;
@ -82,19 +84,27 @@ public class HttpEndpoint implements Endpoint<HttpEndpointDescriptor> {
}
@Override
public void resolveUriTemplate(ServerRequest serverRequest) throws IOException {
if (pathMatcher.match(prefix + path, serverRequest.getEffectiveRequestPath())) {
QueryParameters queryParameters = pathMatcher.extractUriTemplateVariables(prefix + path,
serverRequest.getEffectiveRequestPath());
public ServerRequest resolveRequest(ServerRequest.Builder serverRequestBuilder,
Domain<? extends EndpointResolver<? extends Endpoint<?>>> domain,
EndpointResolver<? extends Endpoint<?>> endpointResolver) {
List<String> context = pathMatcher.tokenizePath(getPrefix());
serverRequestBuilder.setDomain(domain)
.setEndpointResolver(endpointResolver)
.setEndpoint((this))
.setContext(context);
String pattern = prefix + path;
String effectiveRequestPath = serverRequestBuilder.getEffectiveRequestPath();
if (pathMatcher.match(pattern, effectiveRequestPath)) {
QueryParameters queryParameters = pathMatcher.extractUriTemplateVariables(pattern, effectiveRequestPath);
for (Pair<String, String> pair : queryParameters) {
serverRequest.addPathParameter(pair.getFirst(), pair.getSecond());
serverRequestBuilder.addPathParameter(pair.getFirst(), pair.getSecond());
}
}
return serverRequestBuilder.build();
}
@Override
public void before(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
serverRequest.setContext(pathMatcher.tokenizePath(getPrefix()));
if (serverResponse != null) {
for (Filter filter : beforeFilters) {
filter.handle(serverRequest, serverResponse);
@ -104,7 +114,6 @@ public class HttpEndpoint implements Endpoint<HttpEndpointDescriptor> {
@Override
public void after(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
serverRequest.setContext(pathMatcher.tokenizePath(getPrefix()));
if (serverResponse != null) {
for (Filter filter : afterFilters) {
filter.handle(serverRequest, serverResponse);

View file

@ -2,9 +2,6 @@ package org.xbib.netty.http.server.endpoint;
import org.xbib.netty.http.common.HttpMethod;
import org.xbib.netty.http.server.api.EndpointDescriptor;
import org.xbib.netty.http.server.api.ServerRequest;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
public class HttpEndpointDescriptor implements EndpointDescriptor, Comparable<HttpEndpointDescriptor> {
@ -14,10 +11,10 @@ public class HttpEndpointDescriptor implements EndpointDescriptor, Comparable<Ht
private final String contentType;
public HttpEndpointDescriptor(ServerRequest serverRequest) {
this.path = extractPath(serverRequest.getRequestURI());
this.method = Enum.valueOf(HttpMethod.class, serverRequest.getMethod().name());
this.contentType = serverRequest.getHeaders().get(CONTENT_TYPE);
public HttpEndpointDescriptor(String path, HttpMethod method, String contentType) {
this.path = path;
this.method = method;
this.contentType = contentType;
}
@Override
@ -56,13 +53,4 @@ public class HttpEndpointDescriptor implements EndpointDescriptor, Comparable<Ht
public int compareTo(HttpEndpointDescriptor o) {
return toString().compareTo(o.toString());
}
private static String extractPath(String uri) {
String path = uri;
int pos = uri.lastIndexOf('#');
path = pos >= 0 ? path.substring(0, pos) : path;
pos = uri.lastIndexOf('?');
path = pos >= 0 ? path.substring(0, pos) : path;
return path;
}
}

View file

@ -1,8 +1,9 @@
package org.xbib.netty.http.server.endpoint;
import org.xbib.netty.http.common.HttpMethod;
import org.xbib.netty.http.common.util.LimitedConcurrentHashMap;
import org.xbib.netty.http.server.api.EndpointDispatcher;
import org.xbib.netty.http.server.api.EndpointResolver;
import org.xbib.netty.http.server.api.Filter;
import org.xbib.netty.http.server.api.ServerRequest;
import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.api.annotation.Endpoint;
@ -23,27 +24,25 @@ public class HttpEndpointResolver implements EndpointResolver<HttpEndpoint> {
private final List<HttpEndpoint> endpoints;
private final EndpointDispatcher<HttpEndpoint> endpointDispatcher;
private final Filter dispatcher;
private final Map<HttpEndpointDescriptor, List<HttpEndpoint>> endpointDescriptors;
private HttpEndpointResolver(List<HttpEndpoint> endpoints,
EndpointDispatcher<HttpEndpoint> endpointDispatcher,
int limit) {
Objects.requireNonNull(endpointDispatcher);
Filter dispatcher,
Integer limit) {
this.endpoints = endpoints;
this.endpointDispatcher = endpointDispatcher;
this.endpointDescriptors = new LimitedConcurrentHashMap<>(limit);
this.dispatcher = dispatcher;
this.endpointDescriptors = new LimitedConcurrentHashMap<>(limit != null ? limit : DEFAULT_LIMIT);
}
/**
* Find matching endpoints for a server request.
* @param serverRequest the server request
* @return a
*/
@Override
public List<HttpEndpoint> matchingEndpointsFor(ServerRequest serverRequest) {
HttpEndpointDescriptor httpEndpointDescriptor = new HttpEndpointDescriptor(serverRequest);
public List<HttpEndpoint> matchingEndpointsFor(String path, HttpMethod method, String contentType) {
HttpEndpointDescriptor httpEndpointDescriptor = new HttpEndpointDescriptor(path, method, contentType);
endpointDescriptors.putIfAbsent(httpEndpointDescriptor, endpoints.stream()
.filter(endpoint -> endpoint.matches(httpEndpointDescriptor))
.sorted(new HttpEndpoint.EndpointPathComparator(httpEndpointDescriptor.getSortKey()))
@ -52,30 +51,12 @@ public class HttpEndpointResolver implements EndpointResolver<HttpEndpoint> {
}
@Override
public void resolve(List<HttpEndpoint> matchingEndpoints,
ServerRequest serverRequest) throws IOException {
Objects.requireNonNull(matchingEndpoints);
for (HttpEndpoint endpoint : matchingEndpoints) {
endpoint.resolveUriTemplate(serverRequest);
endpoint.before(serverRequest, null);
break;
}
}
@Override
public void handle(List<HttpEndpoint> matchingEndpoints,
public void handle(HttpEndpoint endpoint,
ServerRequest serverRequest,
ServerResponse serverResponse) throws IOException {
Objects.requireNonNull(matchingEndpoints);
for (HttpEndpoint endpoint : matchingEndpoints) {
endpoint.resolveUriTemplate(serverRequest);
endpoint.before(serverRequest, serverResponse);
endpointDispatcher.dispatch(endpoint, serverRequest, serverResponse);
dispatcher.handle(serverRequest, serverResponse);
endpoint.after(serverRequest, serverResponse);
if (serverResponse != null && serverResponse.getStatus() != null) {
break;
}
}
}
public static Builder builder() {
@ -86,11 +67,11 @@ public class HttpEndpointResolver implements EndpointResolver<HttpEndpoint> {
private final List<HttpEndpoint> endpoints;
private int limit;
private Integer limit;
private String prefix;
private EndpointDispatcher<HttpEndpoint> endpointDispatcher;
private Filter dispatcher;
Builder() {
this.limit = DEFAULT_LIMIT;
@ -98,7 +79,7 @@ public class HttpEndpointResolver implements EndpointResolver<HttpEndpoint> {
}
public Builder setLimit(int limit) {
this.limit = limit > 0 ? limit < 1024 * DEFAULT_LIMIT ? limit : DEFAULT_LIMIT : DEFAULT_LIMIT;
this.limit = limit;
return this;
}
@ -153,17 +134,20 @@ public class HttpEndpointResolver implements EndpointResolver<HttpEndpoint> {
return this;
}
public Builder setDispatcher(EndpointDispatcher<HttpEndpoint> endpointDispatcher) {
Objects.requireNonNull(endpointDispatcher);
this.endpointDispatcher = endpointDispatcher;
public Builder setDispatcher(Filter dispatcher) {
Objects.requireNonNull(dispatcher);
this.dispatcher = dispatcher;
return this;
}
public HttpEndpointResolver build() {
Objects.requireNonNull(endpoints);
Objects.requireNonNull(dispatcher);
Objects.requireNonNull(limit);
if (endpoints.isEmpty()) {
throw new IllegalArgumentException("no endpoints configured");
}
return new HttpEndpointResolver(endpoints, endpointDispatcher, limit);
return new HttpEndpointResolver(endpoints, dispatcher, limit);
}
}
}

View file

@ -6,6 +6,7 @@ import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.stream.ChunkedNioStream;
import io.netty.util.AsciiString;
import org.xbib.netty.http.common.util.DateTimeUtil;
import org.xbib.netty.http.server.api.Filter;
import org.xbib.netty.http.server.api.FilterConfig;
@ -58,24 +59,32 @@ public abstract class ResourceService implements Filter {
protected abstract int getMaxAgeSeconds();
private void handleCachedResource(ServerRequest serverRequest, ServerResponse serverResponse, Resource resource) {
private void handleCachedResource(ServerRequest serverRequest, ServerResponse serverResponse, Resource resource) throws IOException {
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER, "resource = " + resource);
}
if (resource.isDirectory()) {
if (!resource.getResourcePath().isEmpty() && !resource.getResourcePath().endsWith("/")) {
// external redirect to
serverResponse.withHeader(HttpHeaderNames.LOCATION, resource.getResourcePath() + "/");
ServerResponse.write(serverResponse, HttpResponseStatus.MOVED_PERMANENTLY);
serverResponse.getBuilder()
.setHeader(HttpHeaderNames.LOCATION, resource.getResourcePath() + "/")
.setStatus( HttpResponseStatus.MOVED_PERMANENTLY)
.build()
.flush();
return;
} else if (resource.indexFileName() != null) {
// external redirect to default index file in this directory
serverResponse.withHeader(HttpHeaderNames.LOCATION, resource.indexFileName());
ServerResponse.write(serverResponse, HttpResponseStatus.MOVED_PERMANENTLY);
serverResponse.getBuilder()
.setHeader(HttpHeaderNames.LOCATION, resource.indexFileName())
.setStatus( HttpResponseStatus.MOVED_PERMANENTLY)
.build()
.flush();
return;
} else {
// send forbidden, we do not allow directory access
ServerResponse.write(serverResponse, HttpResponseStatus.FORBIDDEN);
serverResponse.getBuilder()
.setStatus(HttpResponseStatus.FORBIDDEN)
.build().flush();
return;
}
}
@ -88,8 +97,9 @@ public abstract class ResourceService implements Filter {
String contentType = MimeTypeUtils.guessFromPath(resource.getResourcePath(), false);
long expirationMillis = System.currentTimeMillis() + 1000 * getMaxAgeSeconds();
if (isCacheResponseEnabled()) {
serverResponse.withHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatRfc1123(expirationMillis))
.withHeader(HttpHeaderNames.CACHE_CONTROL, "public, max-age=" + getMaxAgeSeconds());
serverResponse.getBuilder()
.setHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatRfc1123(expirationMillis))
.setHeader(HttpHeaderNames.CACHE_CONTROL, "public, max-age=" + getMaxAgeSeconds());
}
boolean sent = false;
if (isETagResponseEnabled()) {
@ -98,38 +108,48 @@ public abstract class ResourceService implements Filter {
Instant ifUnmodifiedSinceInstant = DateTimeUtil.parseDate(headers.get(HttpHeaderNames.IF_UNMODIFIED_SINCE));
if (ifUnmodifiedSinceInstant != null &&
ifUnmodifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) {
ServerResponse.write(serverResponse, HttpResponseStatus.PRECONDITION_FAILED);
serverResponse.getBuilder()
.setStatus(HttpResponseStatus.PRECONDITION_FAILED)
.build().flush();
return;
}
String ifMatch = headers.get(HttpHeaderNames.IF_MATCH);
if (ifMatch != null && !matches(ifMatch, eTag)) {
ServerResponse.write(serverResponse, HttpResponseStatus.PRECONDITION_FAILED);
serverResponse.getBuilder()
.setStatus(HttpResponseStatus.PRECONDITION_FAILED)
.build().flush();
return;
}
String ifNoneMatch = headers.get(HttpHeaderNames.IF_NONE_MATCH);
if (ifNoneMatch != null && matches(ifNoneMatch, eTag)) {
serverResponse.withHeader(HttpHeaderNames.ETAG, eTag)
.withHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatRfc1123(expirationMillis));
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_MODIFIED);
serverResponse.getBuilder()
.setHeader(HttpHeaderNames.ETAG, eTag)
.setHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatRfc1123(expirationMillis))
.setStatus(HttpResponseStatus.NOT_MODIFIED)
.build().flush();
return;
}
Instant ifModifiedSinceInstant = DateTimeUtil.parseDate(headers.get(HttpHeaderNames.IF_MODIFIED_SINCE));
if (ifModifiedSinceInstant != null &&
ifModifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) {
serverResponse.withHeader(HttpHeaderNames.ETAG, eTag)
.withHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatRfc1123(expirationMillis));
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_MODIFIED);
serverResponse.getBuilder()
.setHeader(HttpHeaderNames.ETAG, eTag)
.setHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatRfc1123(expirationMillis))
.setStatus(HttpResponseStatus.NOT_MODIFIED)
.build().flush();
return;
}
serverResponse.withHeader(HttpHeaderNames.ETAG, eTag)
.withHeader(HttpHeaderNames.LAST_MODIFIED, DateTimeUtil.formatRfc1123(lastModifiedInstant));
serverResponse.getBuilder()
.setHeader(HttpHeaderNames.ETAG, eTag)
.setHeader(HttpHeaderNames.LAST_MODIFIED, DateTimeUtil.formatRfc1123(lastModifiedInstant));
if (isRangeResponseEnabled()) {
performRangeResponse(serverRequest, serverResponse, resource, contentType, eTag, headers);
sent = true;
}
}
if (!sent) {
serverResponse.withHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(resource.getLength()));
serverResponse.getBuilder()
.setHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(resource.getLength()));
send(resource.getURL(), contentType, serverRequest, serverResponse);
}
}
@ -137,16 +157,18 @@ public abstract class ResourceService implements Filter {
private void performRangeResponse(ServerRequest serverRequest, ServerResponse serverResponse,
Resource resource,
String contentType, String eTag,
HttpHeaders headers) {
HttpHeaders headers) throws IOException {
long length = resource.getLength();
serverResponse.withHeader(HttpHeaderNames.ACCEPT_RANGES, "bytes");
serverResponse.getBuilder().setHeader(HttpHeaderNames.ACCEPT_RANGES, "bytes");
Range full = new Range(0, length - 1, length);
List<Range> ranges = new ArrayList<>();
String range = headers.get(HttpHeaderNames.RANGE);
if (range != null) {
if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) {
serverResponse.withHeader(HttpHeaderNames.CONTENT_RANGE, "bytes */" + length);
ServerResponse.write(serverResponse, HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
serverResponse.getBuilder()
.setHeader(HttpHeaderNames.CONTENT_RANGE, "bytes */" + length)
.setStatus(HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE)
.build().flush();
return;
}
String ifRange = headers.get(HttpHeaderNames.IF_RANGE);
@ -171,8 +193,10 @@ public abstract class ResourceService implements Filter {
end = length - 1;
}
if (start > end) {
serverResponse.withHeader(HttpHeaderNames.CONTENT_RANGE, "bytes */" + length);
ServerResponse.write(serverResponse, HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
serverResponse.getBuilder()
.setHeader(HttpHeaderNames.CONTENT_RANGE, "bytes */" + length)
.setStatus(HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE)
.build().flush();
return;
}
ranges.add(new Range(start, end, length));
@ -180,16 +204,19 @@ public abstract class ResourceService implements Filter {
}
}
if (ranges.isEmpty() || ranges.get(0) == full) {
serverResponse.withHeader(HttpHeaderNames.CONTENT_RANGE, "bytes " + full.start + '-' + full.end + '/' + full.total)
.withHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(full.length));
serverResponse.getBuilder()
.setHeader(HttpHeaderNames.CONTENT_RANGE, "bytes " + full.start + '-' + full.end + '/' + full.total)
.setHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(full.length));
send(resource.getURL(), HttpResponseStatus.OK, contentType, serverRequest, serverResponse, full.start, full.length);
} else if (ranges.size() == 1) {
Range r = ranges.get(0);
serverResponse.withHeader(HttpHeaderNames.CONTENT_RANGE, "bytes " + r.start + '-' + r.end + '/' + r.total)
.withHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(r.length));
serverResponse.getBuilder()
.setHeader(HttpHeaderNames.CONTENT_RANGE, "bytes " + r.start + '-' + r.end + '/' + r.total)
.setHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(r.length));
send(resource.getURL(), HttpResponseStatus.PARTIAL_CONTENT, contentType, serverRequest, serverResponse, r.start, r.length);
} else {
serverResponse.withHeader(HttpHeaderNames.CONTENT_TYPE, "multipart/byteranges; boundary=MULTIPART_BOUNDARY");
serverResponse.getBuilder()
.setHeader(HttpHeaderNames.CONTENT_TYPE, "multipart/byteranges; boundary=MULTIPART_BOUNDARY");
StringBuilder sb = new StringBuilder();
for (Range r : ranges) {
try {
@ -203,7 +230,11 @@ public abstract class ResourceService implements Filter {
logger.log(Level.FINEST, e.getMessage(), e);
}
}
ServerResponse.write(serverResponse, HttpResponseStatus.OK, contentType, CharBuffer.wrap(sb), StandardCharsets.ISO_8859_1);
serverResponse.getBuilder()
.setStatus(HttpResponseStatus.OK)
.setContentType(contentType)
.build()
.write(CharBuffer.wrap(sb), StandardCharsets.ISO_8859_1);
}
}
@ -219,37 +250,50 @@ public abstract class ResourceService implements Filter {
}
private void send(URL url, String contentType,
ServerRequest serverRequest, ServerResponse serverResponse) {
ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
if (url == null) {
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
serverResponse.getBuilder()
.setStatus( HttpResponseStatus.NOT_FOUND)
.build().flush();
} else if (serverRequest.getMethod() == HttpMethod.HEAD) {
ServerResponse.write(serverResponse, HttpResponseStatus.OK, contentType);
serverResponse.getBuilder()
.setStatus( HttpResponseStatus.OK)
.setContentType(contentType)
.build().flush();
} else {
if ("file".equals(url.getProtocol())) {
try {
send((FileChannel) Files.newByteChannel(Paths.get(url.toURI())),
HttpResponseStatus.OK, contentType, serverResponse);
send((FileChannel) Files.newByteChannel(Paths.get(url.toURI())), contentType, serverResponse);
} catch (URISyntaxException | IOException e) {
logger.log(Level.SEVERE, e.getMessage(), e);
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
serverResponse.getBuilder()
.setStatus( HttpResponseStatus.NOT_FOUND)
.build().flush();
}
} else {
try (InputStream inputStream = url.openStream()) {
send(inputStream, HttpResponseStatus.OK, contentType, serverResponse);
send(inputStream, contentType, serverResponse);
} catch (IOException e) {
logger.log(Level.SEVERE, e.getMessage(), e);
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
serverResponse.getBuilder()
.setStatus( HttpResponseStatus.NOT_FOUND)
.build().flush();
}
}
}
}
private void send(URL url, HttpResponseStatus httpResponseStatus, String contentType,
ServerRequest serverRequest, ServerResponse serverResponse, long offset, long size) {
ServerRequest serverRequest, ServerResponse serverResponse, long offset, long size) throws IOException {
if (url == null) {
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
serverResponse.getBuilder()
.setStatus( HttpResponseStatus.NOT_FOUND)
.build().flush();
} else if (serverRequest.getMethod() == HttpMethod.HEAD) {
ServerResponse.write(serverResponse, HttpResponseStatus.OK, contentType);
serverResponse.getBuilder()
.setStatus( HttpResponseStatus.OK)
.setContentType(contentType)
.build().flush();
} else {
if ("file".equals(url.getProtocol())) {
Path path = null;
@ -259,52 +303,66 @@ public abstract class ResourceService implements Filter {
contentType, serverResponse, offset, size);
} catch (URISyntaxException | IOException e) {
logger.log(Level.SEVERE, e.getMessage() + " path=" + path, e);
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
serverResponse.getBuilder()
.setStatus( HttpResponseStatus.NOT_FOUND)
.build().flush();
}
} else {
try (InputStream inputStream = url.openStream()) {
send(inputStream, httpResponseStatus, contentType, serverResponse, offset, size);
} catch (IOException e) {
logger.log(Level.SEVERE, e.getMessage(), e);
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
serverResponse.getBuilder()
.setStatus( HttpResponseStatus.NOT_FOUND)
.build().flush();
}
}
}
}
private void send(FileChannel fileChannel, HttpResponseStatus httpResponseStatus, String contentType,
private void send(FileChannel fileChannel, String contentType,
ServerResponse serverResponse) throws IOException {
send(fileChannel, httpResponseStatus, contentType, serverResponse, 0L, fileChannel.size());
send(fileChannel, HttpResponseStatus.OK, contentType, serverResponse, 0L, fileChannel.size());
}
private void send(FileChannel fileChannel, HttpResponseStatus httpResponseStatus, String contentType,
ServerResponse serverResponse, long offset, long size) {
ServerResponse serverResponse, long offset, long size) throws IOException {
if (fileChannel == null) {
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
serverResponse.getBuilder()
.setStatus( HttpResponseStatus.NOT_FOUND)
.build().flush();
} else {
MappedByteBuffer mappedByteBuffer = null;
try {
mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, offset, size);
} catch (IOException e) {
// resource is not a file that can be mapped
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
serverResponse.getBuilder()
.setStatus(HttpResponseStatus.NOT_FOUND)
.build().flush();
}
if (mappedByteBuffer != null) {
serverResponse.withStatus(httpResponseStatus)
.withContentType(contentType)
serverResponse.getBuilder()
.setStatus(httpResponseStatus)
.setContentType(contentType)
.build()
.write(Unpooled.wrappedBuffer(mappedByteBuffer));
}
}
}
private void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType,
private void send(InputStream inputStream, String contentType,
ServerResponse serverResponse) throws IOException {
if (inputStream == null) {
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
serverResponse.getBuilder()
.setStatus(HttpResponseStatus.NOT_FOUND)
.build().flush();
} else {
try (ReadableByteChannel channel = Channels.newChannel(inputStream)) {
serverResponse.withStatus(httpResponseStatus)
.withContentType(contentType)
serverResponse.getBuilder()
.setStatus(HttpResponseStatus.OK)
.setContentType(contentType)
.build()
.write(new ChunkedNioStream(channel));
}
}
@ -313,10 +371,14 @@ public abstract class ResourceService implements Filter {
private void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType,
ServerResponse serverResponse, long offset, long size) throws IOException {
if (inputStream == null) {
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
serverResponse.getBuilder()
.setStatus(HttpResponseStatus.NOT_FOUND)
.build().flush();
} else {
serverResponse.withStatus(httpResponseStatus)
.withContentType(contentType)
serverResponse.getBuilder()
.setStatus(httpResponseStatus)
.setContentType(contentType)
.build()
.write(Unpooled.wrappedBuffer(readBuffer(inputStream, offset, size)));
}
}
@ -351,7 +413,7 @@ public abstract class ResourceService implements Filter {
return buf;
}
class Range {
static class Range {
long start;
long end;
long length;

View file

@ -1,46 +0,0 @@
package org.xbib.netty.http.server.handler.stream;
import io.netty.handler.stream.ChunkedNioStream;
import java.io.IOException;
import java.nio.channels.SeekableByteChannel;
/**
* A {@link ChunkedNioStream} that fetches data from a {@link SeekableByteChannel}
* chunk by chunk. Please note that the {@link SeekableByteChannel} must
* operate in blocking mode. Non-blocking mode channels are not supported.
*/
public class SeekableChunkedNioStream extends ChunkedNioStream {
/**
* Creates a new instance that fetches data from the specified channel.
* @param in input
*/
public SeekableChunkedNioStream(SeekableByteChannel in) {
super(in);
}
/**
* Creates a new instance that fetches data from the specified channel.
*
* @param in channel
* @param chunkSize the number of bytes to fetch on each call
*/
public SeekableChunkedNioStream(SeekableByteChannel in, int chunkSize) {
super(in, chunkSize);
}
/**
* Creates a new instance that fetches data from the specified channel.
*
* @param in channel
* @param position the position in the byte channel
* @param chunkSize the number of bytes to fetch on each call
* @throws IOException if creation fails
*/
public SeekableChunkedNioStream(SeekableByteChannel in, long position, int chunkSize) throws IOException {
super(in, chunkSize);
in.position(position);
}
}

View file

@ -1,8 +1,6 @@
package org.xbib.netty.http.server;
package org.xbib.netty.http.server.protocol.http1;
import org.xbib.netty.http.server.api.ServerProtocolProvider;
import org.xbib.netty.http.server.handler.http.Http1ChannelInitializer;
import org.xbib.netty.http.server.transport.Http1Transport;
public class Http1 implements ServerProtocolProvider<Http1ChannelInitializer, Http1Transport> {

View file

@ -1,4 +1,4 @@
package org.xbib.netty.http.server.handler.http;
package org.xbib.netty.http.server.protocol.http1;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
@ -28,7 +28,6 @@ import org.xbib.netty.http.server.handler.ExtendedSNIHandler;
import org.xbib.netty.http.server.handler.IdleTimeoutHandler;
import org.xbib.netty.http.server.handler.TrafficLoggingHandler;
import org.xbib.netty.http.server.api.ServerTransport;
import java.nio.charset.StandardCharsets;
import java.util.logging.Level;
import java.util.logging.Logger;

View file

@ -0,0 +1,75 @@
package org.xbib.netty.http.server.protocol.http1;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http2.Http2Settings;
import io.netty.handler.ssl.SslHandler;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.AcceptState;
import org.xbib.netty.http.server.BaseTransport;
import org.xbib.netty.http.server.HttpServerRequest;
import java.io.IOException;
import java.net.InetSocketAddress;
public class Http1Transport extends BaseTransport {
public Http1Transport(Server server) {
super(server);
}
@Override
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException {
AcceptState acceptState = acceptRequest(server.getServerConfig().getAddress().getVersion(),
fullHttpRequest.headers());
ServerResponse.Builder serverResponseBuilder = HttpServerResponse.builder(ctx)
.setResponseId(server.getResponseCounter().incrementAndGet());
switch (acceptState) {
case OK: {
HttpServerRequest.Builder serverRequestBuilder = HttpServerRequest.builder()
.setLocalAddress((InetSocketAddress) ctx.channel().localAddress())
.setRemoteAddress((InetSocketAddress) ctx.channel().remoteAddress())
.setHttpRequest(fullHttpRequest.retainedDuplicate())
.setSequenceId(sequenceId)
.setRequestId(server.getRequestCounter().incrementAndGet());
SslHandler sslHandler = ctx.channel().pipeline().get(SslHandler.class);
if (sslHandler != null) {
serverRequestBuilder.setSession(sslHandler.engine().getSession());
}
boolean shouldClose = "close".equalsIgnoreCase(fullHttpRequest.headers().get(HttpHeaderNames.CONNECTION));
serverResponseBuilder.shouldClose(shouldClose);
server.handle(serverRequestBuilder, serverResponseBuilder);
break;
}
case MISSING_HOST_HEADER: {
HttpServerResponse.builder(ctx)
.setStatus(HttpResponseStatus.BAD_REQUEST)
.setContentType("text/plain")
.build()
.write("missing 'Host' header");
}
case EXPECTATION_FAILED: {
HttpServerResponse.builder(ctx)
.setStatus(HttpResponseStatus.EXPECTATION_FAILED)
.build()
.flush();
break;
}
case UNSUPPORTED_HTTP_VERSION: {
HttpServerResponse.builder(ctx)
.setStatus(HttpResponseStatus.BAD_REQUEST)
.setContentType("text/plain")
.build()
.write("unsupported HTTP version");
break;
}
}
}
@Override
public void settingsReceived(ChannelHandlerContext ctx, Http2Settings http2Settings) {
// there are no settings in HTTP 1
}
}

View file

@ -1,4 +1,4 @@
package org.xbib.netty.http.server.handler.http;
package org.xbib.netty.http.server.protocol.http1;
import io.netty.handler.codec.http.LastHttpContent;

View file

@ -1,4 +1,4 @@
package org.xbib.netty.http.server.handler.http;
package org.xbib.netty.http.server.protocol.http1;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.HttpResponse;

View file

@ -1,4 +1,4 @@
package org.xbib.netty.http.server.handler.http;
package org.xbib.netty.http.server.protocol.http1;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;

View file

@ -1,7 +1,8 @@
package org.xbib.netty.http.server.transport;
package org.xbib.netty.http.server.protocol.http1;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
@ -18,14 +19,12 @@ import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.stream.ChunkedInput;
import io.netty.util.AsciiString;
import org.xbib.netty.http.common.cookie.Cookie;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.ServerName;
import org.xbib.netty.http.server.api.ServerRequest;
import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.cookie.ServerCookieEncoder;
import org.xbib.netty.http.server.handler.http.HttpPipelinedResponse;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
@ -38,70 +37,64 @@ public class HttpServerResponse implements ServerResponse {
private static final Logger logger = Logger.getLogger(HttpServerResponse.class.getName());
private final Server server;
private final ServerRequest serverRequest;
private final Builder builder;
private final ChannelHandlerContext ctx;
private HttpHeaders headers;
private final HttpHeaders headers;
private HttpHeaders trailingHeaders;
private final HttpHeaders trailingHeaders;
private HttpResponseStatus httpResponseStatus;
private final HttpResponseStatus httpResponseStatus;
HttpServerResponse(Server server, HttpServerRequest serverRequest, ChannelHandlerContext ctx) {
this.server = server;
this.serverRequest = serverRequest;
this.ctx = ctx;
this.headers = new DefaultHttpHeaders();
this.trailingHeaders = new DefaultHttpHeaders();
private final boolean shouldClose;
private final boolean shouldAddServerName;
private final Integer sequenceId;
private final Integer streamId;
private final Long responseId;
private final CharSequence contentType;
private HttpServerResponse(Builder builder) {
this.builder = builder;
this.ctx = builder.ctx;
this.headers = builder.headers;
this.trailingHeaders = builder.trailingHeaders;
this.httpResponseStatus = builder.httpResponseStatus;
this.shouldClose = builder.shouldClose;
this.shouldAddServerName = builder.shouldAddServerName;
this.sequenceId = builder.sequenceId;
this.streamId = builder.streamId;
this.responseId = builder.responseId;
this.contentType = builder.contentType;
}
@Override
public ServerResponse withHeader(CharSequence name, String value) {
headers.set(name, value);
return this;
public Builder getBuilder() {
return builder;
}
@Override
public ChannelHandlerContext getChannelHandlerContext() {
return ctx;
public Integer getStreamId() {
return streamId;
}
@Override
public HttpResponseStatus getStatus() {
return httpResponseStatus;
public Integer getSequenceId() {
return sequenceId;
}
@Override
public ServerResponse withStatus(HttpResponseStatus httpResponseStatus) {
this.httpResponseStatus = httpResponseStatus;
return this;
public Long getResponseId() {
return responseId;
}
@Override
public ServerResponse withContentType(String contentType) {
headers.remove(HttpHeaderNames.CONTENT_TYPE);
headers.add(HttpHeaderNames.CONTENT_TYPE, contentType);
return this;
}
@Override
public ServerResponse withCharset(Charset charset) {
CharSequence contentType = headers.get(HttpHeaderNames.CONTENT_TYPE);
if (contentType != null) {
headers.remove(HttpHeaderNames.CONTENT_TYPE);
headers.add(HttpHeaderNames.CONTENT_TYPE, contentType + "; charset=" + charset.name());
}
return this;
}
@Override
public ServerResponse withCookie(Cookie cookie) {
Objects.requireNonNull(cookie);
headers.add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie));
return this;
public static Builder builder(ChannelHandlerContext ctx) {
return new Builder(ctx);
}
@Override
@ -114,6 +107,16 @@ public class HttpServerResponse implements ServerResponse {
write(Unpooled.buffer(0));
}
@Override
public void write(String string) {
write(ByteBufUtil.writeUtf8(ctx.alloc(), string));
}
@Override
public void write(CharBuffer charBuffer, Charset charset) {
write(ByteBufUtil.encodeString(ctx.alloc(), charBuffer, charset));
}
@Override
public void write(byte[] bytes) {
ByteBuf byteBuf = ctx.alloc().buffer(bytes.length);
@ -129,37 +132,30 @@ public class HttpServerResponse implements ServerResponse {
@Override
public void write(ByteBuf byteBuf) {
Objects.requireNonNull(byteBuf);
if (httpResponseStatus == null) {
httpResponseStatus = HttpResponseStatus.OK;
}
CharSequence contentType = headers.get(HttpHeaderNames.CONTENT_TYPE);
if (contentType == null) {
headers.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_OCTET_STREAM);
}
headers.add(HttpHeaderNames.CONTENT_TYPE, contentType);
if (httpResponseStatus.code() >= 200 && httpResponseStatus.code() != 204) {
if (!headers.contains(HttpHeaderNames.CONTENT_LENGTH) && !headers.contains(HttpHeaderNames.TRANSFER_ENCODING)) {
headers.add(HttpHeaderNames.CONTENT_LENGTH, Long.toString(byteBuf.readableBytes()));
}
}
if (serverRequest != null && "close".equalsIgnoreCase(serverRequest.getHeaders().get(HttpHeaderNames.CONNECTION)) &&
!headers.contains(HttpHeaderNames.CONNECTION)) {
if (shouldClose) {
headers.add(HttpHeaderNames.CONNECTION, "close");
}
if (!headers.contains(HttpHeaderNames.DATE)) {
headers.add(HttpHeaderNames.DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC)));
}
if (shouldAddServerName) {
headers.add(HttpHeaderNames.SERVER, ServerName.getServerName());
}
if (ctx.channel().isWritable()) {
FullHttpResponse fullHttpResponse;
fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, httpResponseStatus, byteBuf, headers, trailingHeaders);
if (serverRequest != null && serverRequest.getSequenceId() != null) {
if (sequenceId != null) {
HttpPipelinedResponse httpPipelinedResponse = new HttpPipelinedResponse(fullHttpResponse,
ctx.channel().newPromise(), serverRequest.getSequenceId());
ctx.channel().newPromise(), sequenceId);
ctx.channel().writeAndFlush(httpPipelinedResponse);
server.getResponseCounter().incrementAndGet();
} else {
ctx.channel().writeAndFlush(fullHttpResponse);
server.getResponseCounter().incrementAndGet();
}
} else {
logger.log(Level.WARNING, "channel not writeable: " + ctx.channel());
@ -174,9 +170,6 @@ public class HttpServerResponse implements ServerResponse {
@Override
public void write(ChunkedInput<ByteBuf> chunkedInput) {
Objects.requireNonNull(chunkedInput);
if (httpResponseStatus == null) {
httpResponseStatus = HttpResponseStatus.OK;
}
CharSequence contentType = headers.get(HttpHeaderNames.CONTENT_TYPE);
if (contentType == null) {
headers.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_OCTET_STREAM);
@ -192,12 +185,117 @@ public class HttpServerResponse implements ServerResponse {
logger.log(Level.FINEST, httpResponse.headers()::toString);
ctx.channel().write(httpResponse);
ChannelFuture channelFuture = ctx.channel().writeAndFlush(new HttpChunkedInput(chunkedInput));
if ("close".equalsIgnoreCase(serverRequest.getHeaders().get(HttpHeaderNames.CONNECTION)) &&
!headers.contains(HttpHeaderNames.CONNECTION)) {
if (shouldClose) {
channelFuture.addListener(ChannelFutureListener.CLOSE);
}
} else {
logger.log(Level.WARNING, "channel not writeable");
}
}
public static class Builder implements ServerResponse.Builder {
private final ChannelHandlerContext ctx;
private final HttpHeaders headers;
private final HttpHeaders trailingHeaders;
private HttpResponseStatus httpResponseStatus;
private boolean shouldClose;
private boolean shouldAddServerName;
private Integer sequenceId;
private Integer streamId;
private Long responseId;
private CharSequence contentType;
private Builder(ChannelHandlerContext ctx) {
this.ctx = ctx;
this.httpResponseStatus = HttpResponseStatus.OK;
this.headers = new DefaultHttpHeaders();
this.trailingHeaders = new DefaultHttpHeaders();
this.contentType = HttpHeaderValues.APPLICATION_OCTET_STREAM;
}
@Override
public Builder setStatus(HttpResponseStatus httpResponseStatus) {
this.httpResponseStatus = httpResponseStatus;
return this;
}
@Override
public ServerResponse.Builder setContentType(CharSequence contentType) {
this.contentType = contentType;
return this;
}
@Override
public Builder setHeader(CharSequence name, String value) {
headers.set(name, value);
return this;
}
@Override
public Builder setTrailingHeader(CharSequence name, String value) {
trailingHeaders.set(name, value);
return this;
}
@Override
public Builder setCharset(Charset charset) {
if (contentType != null) {
this.contentType = AsciiString.of(contentType.toString() + "; charset=" + charset.name());
}
return this;
}
@Override
public Builder addCookie(Cookie cookie) {
Objects.requireNonNull(cookie);
headers.add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie));
return this;
}
@Override
public Builder shouldClose(boolean shouldClose) {
this.shouldClose = shouldClose;
return this;
}
@Override
public Builder shouldAddServerName(boolean shouldAddServerName) {
this.shouldAddServerName = shouldAddServerName;
return this;
}
@Override
public Builder setSequenceId(Integer sequenceId) {
this.sequenceId = sequenceId;
return this;
}
@Override
public Builder setStreamId(Integer streamId) {
this.streamId = streamId;
return this;
}
@Override
public Builder setResponseId(Long responseId) {
this.responseId = responseId;
return this;
}
@Override
public ServerResponse build() {
return new HttpServerResponse(this);
}
}
}

View file

@ -1,8 +1,6 @@
package org.xbib.netty.http.server;
package org.xbib.netty.http.server.protocol.http2;
import org.xbib.netty.http.server.api.ServerProtocolProvider;
import org.xbib.netty.http.server.handler.http2.Http2ChannelInitializer;
import org.xbib.netty.http.server.transport.Http2Transport;
public class Http2 implements ServerProtocolProvider<Http2ChannelInitializer, Http2Transport> {

View file

@ -1,4 +1,4 @@
package org.xbib.netty.http.server.handler.http2;
package org.xbib.netty.http.server.protocol.http2;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;

View file

@ -0,0 +1,303 @@
package org.xbib.netty.http.server.protocol.http2;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpChunkedInput;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http2.DefaultHttp2DataFrame;
import io.netty.handler.codec.http2.DefaultHttp2Headers;
import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame;
import io.netty.handler.codec.http2.Http2DataFrame;
import io.netty.handler.codec.http2.Http2Headers;
import io.netty.handler.codec.http2.Http2HeadersFrame;
import io.netty.handler.codec.http2.HttpConversionUtil;
import io.netty.handler.stream.ChunkedInput;
import org.xbib.netty.http.common.cookie.Cookie;
import org.xbib.netty.http.server.ServerName;
import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.cookie.ServerCookieEncoder;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Http2ServerResponse implements ServerResponse {
private static final Logger logger = Logger.getLogger(Http2ServerResponse.class.getName());
private final Builder builder;
private final ChannelHandlerContext ctx;
private final Http2Headers headers;
private final Http2Headers trailingHeaders;
private final HttpResponseStatus httpResponseStatus;
private final boolean shouldClose;
private final boolean shouldAddServerName;
private final Integer sequenceId;
private final Integer streamId;
private final Long responseId;
private final CharSequence contentType;
private Http2ServerResponse(Builder builder) {
this.builder = builder;
this.ctx = builder.ctx;
this.headers = builder.headers;
this.trailingHeaders = builder.trailingHeaders;
this.httpResponseStatus = builder.httpResponseStatus;
this.shouldClose = builder.shouldClose;
this.shouldAddServerName = builder.shouldAddServerName;
this.sequenceId = builder.sequenceId;
this.streamId = builder.streamId;
this.responseId = builder.responseId;
this.contentType = builder.contentType;
}
@Override
public Builder getBuilder() {
return builder;
}
@Override
public Integer getStreamId() {
return streamId;
}
@Override
public Integer getSequenceId() {
return sequenceId;
}
@Override
public Long getResponseId() {
return responseId;
}
public static Builder builder(ChannelHandlerContext ctx) {
return new Builder(ctx);
}
@Override
public ByteBufOutputStream getOutputStream() {
return new ByteBufOutputStream(ctx.alloc().buffer());
}
@Override
public void flush() {
write((ByteBuf) null);
}
@Override
public void write(String string) {
write(ByteBufUtil.writeUtf8(ctx.alloc(), string));
}
@Override
public void write(CharBuffer charBuffer, Charset charset) {
write(ByteBufUtil.encodeString(ctx.alloc(), charBuffer, charset));
}
@Override
public void write(byte[] bytes) {
ByteBuf byteBuf = ctx.alloc().buffer(bytes.length);
byteBuf.writeBytes(bytes);
write(byteBuf);
}
@Override
public void write(ByteBufOutputStream byteBufOutputStream) {
write(byteBufOutputStream.buffer());
}
@Override
public void write(ByteBuf byteBuf) {
headers.add(HttpHeaderNames.CONTENT_TYPE, contentType);
if (!headers.contains(HttpHeaderNames.CONTENT_LENGTH) && !headers.contains(HttpHeaderNames.TRANSFER_ENCODING)) {
if (byteBuf != null) {
headers.add(HttpHeaderNames.CONTENT_LENGTH, Long.toString(byteBuf.readableBytes()));
}
}
if (shouldClose) {
headers.add(HttpHeaderNames.CONNECTION, "close");
}
if (!headers.contains(HttpHeaderNames.DATE)) {
headers.add(HttpHeaderNames.DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC)));
}
if (shouldAddServerName) {
headers.add(HttpHeaderNames.SERVER, ServerName.getServerName());
}
if (streamId != null) {
headers.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), streamId);
}
if (ctx.channel().isWritable()) {
Http2Headers http2Headers = new DefaultHttp2Headers().status(httpResponseStatus.codeAsText()).add(headers);
Http2HeadersFrame http2HeadersFrame = new DefaultHttp2HeadersFrame(http2Headers, byteBuf == null);
ctx.channel().write(http2HeadersFrame);
if (byteBuf != null) {
Http2DataFrame http2DataFrame = new DefaultHttp2DataFrame(byteBuf, true);
ctx.channel().write(http2DataFrame);
}
/*if (trailingHeaders != null) {
Http2Headers trailingHttp2Headers = new DefaultHttp2Headers().add(trailingHeaders);
Http2HeadersFrame trailingHttp2HeadersFrame = new DefaultHttp2HeadersFrame(trailingHttp2Headers, byteBuf == null);
ctx.channel().write(trailingHttp2HeadersFrame);
}*/
ctx.channel().flush();
} else {
logger.log(Level.WARNING, "channel is not writeable: " + ctx.channel());
}
}
/**
* Chunked response from a readable byte channel.
*
* @param chunkedInput chunked input
*/
@Override
public void write(ChunkedInput<ByteBuf> chunkedInput) {
Objects.requireNonNull(chunkedInput);
CharSequence contentType = headers.get(HttpHeaderNames.CONTENT_TYPE);
if (contentType == null) {
headers.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_OCTET_STREAM);
}
headers.add(HttpHeaderNames.TRANSFER_ENCODING, "chunked");
if (!headers.contains(HttpHeaderNames.DATE)) {
headers.add(HttpHeaderNames.DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC)));
}
headers.add(HttpHeaderNames.SERVER, ServerName.getServerName());
if (ctx.channel().isWritable()) {
Http2Headers http2Headers = new DefaultHttp2Headers().status(httpResponseStatus.codeAsText()).add(headers);
Http2HeadersFrame http2HeadersFrame = new DefaultHttp2HeadersFrame(http2Headers,false);
ctx.channel().write(http2HeadersFrame);
ChannelFuture channelFuture = ctx.channel().writeAndFlush(new HttpChunkedInput(chunkedInput));
if (shouldClose) {
channelFuture.addListener(ChannelFutureListener.CLOSE);
}
} else {
logger.log(Level.WARNING, "channel is not writeable: " + ctx.channel());
}
}
public static class Builder implements ServerResponse.Builder {
private final ChannelHandlerContext ctx;
private final Http2Headers headers;
private final Http2Headers trailingHeaders;
private HttpResponseStatus httpResponseStatus;
private boolean shouldClose;
private boolean shouldAddServerName;
private Integer sequenceId;
private Integer streamId;
private Long responseId;
private CharSequence contentType;
private Builder(ChannelHandlerContext ctx) {
this.ctx = ctx;
this.httpResponseStatus = HttpResponseStatus.OK;
this.headers = new DefaultHttp2Headers();
this.trailingHeaders = new DefaultHttp2Headers();
this.contentType = HttpHeaderValues.APPLICATION_OCTET_STREAM;
}
@Override
public Builder setStatus(HttpResponseStatus httpResponseStatus) {
this.httpResponseStatus = httpResponseStatus;
return this;
}
@Override
public Builder setContentType(CharSequence contentType) {
this.contentType = contentType;
return this;
}
@Override
public Builder setHeader(CharSequence name, String value) {
headers.set(name, value);
return this;
}
@Override
public Builder setTrailingHeader(CharSequence name, String value) {
trailingHeaders.set(name, value);
return this;
}
@Override
public Builder setCharset(Charset charset) {
if (contentType != null) {
this.contentType = contentType + "; charset=" + charset.name();
}
return this;
}
@Override
public Builder addCookie(Cookie cookie) {
Objects.requireNonNull(cookie);
headers.add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie));
return this;
}
@Override
public Builder shouldClose(boolean shouldClose) {
this.shouldClose = shouldClose;
return this;
}
@Override
public Builder shouldAddServerName(boolean shouldAddServerName) {
this.shouldAddServerName = shouldAddServerName;
return this;
}
@Override
public Builder setSequenceId(Integer sequenceId) {
this.sequenceId = sequenceId;
return this;
}
@Override
public Builder setStreamId(Integer streamId) {
this.streamId = streamId;
return this;
}
@Override
public Builder setResponseId(Long responseId) {
this.responseId = responseId;
return this;
}
@Override
public ServerResponse build() {
return new Http2ServerResponse(this);
}
}
}

View file

@ -0,0 +1,84 @@
package org.xbib.netty.http.server.protocol.http2;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http2.Http2Settings;
import io.netty.handler.codec.http2.HttpConversionUtil;
import io.netty.handler.ssl.SslHandler;
import org.xbib.netty.http.server.AcceptState;
import org.xbib.netty.http.server.BaseTransport;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.HttpServerRequest;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Http2Transport extends BaseTransport {
private static final Logger logger = Logger.getLogger(Http2Transport.class.getName());
public Http2Transport(Server server) {
super(server);
}
@Override
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException {
AcceptState acceptState = acceptRequest(server.getServerConfig().getAddress().getVersion(),
fullHttpRequest.headers());
Integer streamId = fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text());
ServerResponse.Builder serverResponseBuilder = Http2ServerResponse.builder(ctx)
.setResponseId(server.getResponseCounter().incrementAndGet())
.setStreamId(streamId)
.setSequenceId(sequenceId);
switch (acceptState) {
case OK: {
HttpServerRequest.Builder serverRequestBuilder = HttpServerRequest.builder()
.setHttpRequest(fullHttpRequest.retainedDuplicate())
.setLocalAddress((InetSocketAddress) ctx.channel().localAddress())
.setRemoteAddress((InetSocketAddress) ctx.channel().remoteAddress())
.setStreamId(streamId)
.setSequenceId(sequenceId)
.setRequestId(server.getRequestCounter().incrementAndGet());
SslHandler sslHandler = ctx.channel().pipeline().get(SslHandler.class);
if (sslHandler != null) {
serverRequestBuilder.setSession(sslHandler.engine().getSession());
}
boolean shouldClose = "close".equalsIgnoreCase(fullHttpRequest.headers().get(HttpHeaderNames.CONNECTION));
serverResponseBuilder.shouldClose(shouldClose);
server.handle(serverRequestBuilder, serverResponseBuilder);
break;
}
case MISSING_HOST_HEADER: {
serverResponseBuilder
.setStatus(HttpResponseStatus.BAD_REQUEST)
.setContentType("text/plain")
.build()
.write("missing 'Host' header");
}
case EXPECTATION_FAILED: {
serverResponseBuilder
.setStatus(HttpResponseStatus.EXPECTATION_FAILED)
.build()
.flush();
break;
}
case UNSUPPORTED_HTTP_VERSION: {
serverResponseBuilder
.setStatus(HttpResponseStatus.BAD_REQUEST)
.setContentType("text/plain")
.build()
.write("unsupported HTTP version");
break;
}
}
}
@Override
public void settingsReceived(ChannelHandlerContext ctx, Http2Settings http2Settings) {
logger.log(Level.FINER, "settings received, ignoring");
}
}

View file

@ -1,70 +0,0 @@
package org.xbib.netty.http.server.transport;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.DefaultServerConfig;
import org.xbib.netty.http.server.api.ServerRequest;
import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.api.ServerTransport;
import java.util.logging.Level;
import java.util.logging.Logger;
abstract class BaseTransport implements ServerTransport {
private static final Logger logger = Logger.getLogger(BaseTransport.class.getName());
protected final Server server;
BaseTransport(Server server) {
this.server = server;
}
@Override
public void exceptionReceived(ChannelHandlerContext ctx, Throwable throwable) {
logger.log(Level.WARNING, throwable.getMessage(), throwable);
}
/**
* Accepts a request, performing various validation checks
* and required special header handling, possibly returning an
* appropriate response.
*
* @param serverConfig the server config
* @param serverRequest the request
* @param serverResponse the response
* @return whether further processing should be performed
*/
static boolean acceptRequest(DefaultServerConfig serverConfig,
ServerRequest serverRequest,
ServerResponse serverResponse) {
HttpVersion version = serverConfig.getAddress().getVersion();
HttpHeaders reqHeaders = serverRequest.getHeaders();
if (version.majorVersion() == 1 || version.majorVersion() == 2) {
if (!reqHeaders.contains(HttpHeaderNames.HOST)) {
// RFC2616#14.23: missing Host header gets 400
ServerResponse.write(serverResponse,
HttpResponseStatus.BAD_REQUEST, "application/octet-stream", "missing 'Host' header");
return false;
}
// return a continue response before reading body
String expect = reqHeaders.get(HttpHeaderNames.EXPECT);
if (expect != null) {
if (!"100-continue".equalsIgnoreCase(expect)) {
// RFC2616#14.20: if unknown expect, send 417
ServerResponse.write(serverResponse, HttpResponseStatus.EXPECTATION_FAILED);
return false;
}
}
} else {
ServerResponse.write(serverResponse, HttpResponseStatus.BAD_REQUEST,
"application/octet-stream", "unsupported HTTP version: " + version);
return false;
}
return true;
}
}

View file

@ -1,43 +0,0 @@
package org.xbib.netty.http.server.transport;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http2.Http2Settings;
import io.netty.handler.ssl.SslHandler;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.api.ServerResponse;
import java.io.IOException;
import java.net.InetSocketAddress;
public class Http1Transport extends BaseTransport {
public Http1Transport(Server server) {
super(server);
}
@Override
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException {
HttpServerRequest serverRequest = new HttpServerRequest(fullHttpRequest,
(InetSocketAddress) ctx.channel().localAddress(),
(InetSocketAddress) ctx.channel().remoteAddress());
serverRequest.setSequenceId(sequenceId);
serverRequest.setRequestId(server.getRequestCounter().incrementAndGet());
SslHandler sslHandler = ctx.channel().pipeline().get(SslHandler.class);
if (sslHandler != null) {
serverRequest.setSession(sslHandler.engine().getSession());
}
HttpServerResponse serverResponse = new HttpServerResponse(server, serverRequest, ctx);
if (acceptRequest(server.getServerConfig(), serverRequest, serverResponse)) {
serverRequest.handleParameters();
server.handle(serverRequest, serverResponse);
} else {
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE);
}
}
@Override
public void settingsReceived(ChannelHandlerContext ctx, Http2Settings http2Settings) {
// there are no settings in HTTP 1
}
}

View file

@ -1,201 +0,0 @@
package org.xbib.netty.http.server.transport;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpChunkedInput;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http2.DefaultHttp2DataFrame;
import io.netty.handler.codec.http2.DefaultHttp2Headers;
import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame;
import io.netty.handler.codec.http2.Http2DataFrame;
import io.netty.handler.codec.http2.Http2Headers;
import io.netty.handler.codec.http2.Http2HeadersFrame;
import io.netty.handler.codec.http2.HttpConversionUtil;
import io.netty.handler.stream.ChunkedInput;
import org.xbib.netty.http.common.cookie.Cookie;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.ServerName;
import org.xbib.netty.http.server.api.ServerRequest;
import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.cookie.ServerCookieEncoder;
import java.nio.charset.Charset;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Http2ServerResponse implements ServerResponse {
private static final Logger logger = Logger.getLogger(Http2ServerResponse.class.getName());
private final Server server;
private final ServerRequest serverRequest;
private final ChannelHandlerContext ctx;
private Http2Headers headers;
private HttpResponseStatus httpResponseStatus;
Http2ServerResponse(Server server, HttpServerRequest serverRequest, ChannelHandlerContext ctx) {
this.server = server;
this.serverRequest = serverRequest;
this.ctx = ctx;
this.headers = new DefaultHttp2Headers();
}
@Override
public ServerResponse withHeader(CharSequence name, String value) {
headers.set(name, value);
return this;
}
@Override
public ServerResponse withCookie(Cookie cookie) {
Objects.requireNonNull(cookie);
headers.add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie));
return this;
}
@Override
public ChannelHandlerContext getChannelHandlerContext() {
return ctx;
}
@Override
public HttpResponseStatus getStatus() {
return httpResponseStatus;
}
@Override
public ServerResponse withStatus(HttpResponseStatus httpResponseStatus) {
this.httpResponseStatus = httpResponseStatus;
return this;
}
@Override
public ServerResponse withContentType(String contentType) {
headers.remove(HttpHeaderNames.CONTENT_TYPE);
headers.add(HttpHeaderNames.CONTENT_TYPE, contentType);
return this;
}
@Override
public ServerResponse withCharset(Charset charset) {
CharSequence contentType = headers.get(HttpHeaderNames.CONTENT_TYPE);
if (contentType != null) {
headers.remove(HttpHeaderNames.CONTENT_TYPE);
headers.add(HttpHeaderNames.CONTENT_TYPE, contentType + "; charset=" + charset.name());
}
return this;
}
@Override
public ByteBufOutputStream getOutputStream() {
return new ByteBufOutputStream(ctx.alloc().buffer());
}
@Override
public void flush() {
write((ByteBuf) null);
}
@Override
public void write(byte[] bytes) {
ByteBuf byteBuf = ctx.alloc().buffer(bytes.length);
byteBuf.writeBytes(bytes);
write(byteBuf);
}
@Override
public void write(ByteBufOutputStream byteBufOutputStream) {
write(byteBufOutputStream.buffer());
}
@Override
public void write(ByteBuf byteBuf) {
if (httpResponseStatus == null) {
httpResponseStatus = HttpResponseStatus.OK;
}
CharSequence contentType = headers.get(HttpHeaderNames.CONTENT_TYPE);
if (contentType == null) {
headers.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_OCTET_STREAM);
}
if (!headers.contains(HttpHeaderNames.CONTENT_LENGTH) && !headers.contains(HttpHeaderNames.TRANSFER_ENCODING)) {
if (byteBuf != null) {
headers.add(HttpHeaderNames.CONTENT_LENGTH, Long.toString(byteBuf.readableBytes()));
}
}
if (serverRequest != null && "close".equalsIgnoreCase(serverRequest.getHeaders().get(HttpHeaderNames.CONNECTION)) &&
!headers.contains(HttpHeaderNames.CONNECTION)) {
headers.add(HttpHeaderNames.CONNECTION, "close");
}
if (!headers.contains(HttpHeaderNames.DATE)) {
headers.add(HttpHeaderNames.DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC)));
}
headers.add(HttpHeaderNames.SERVER, ServerName.getServerName());
if (serverRequest != null) {
Integer streamId = serverRequest.getStreamId();
if (streamId != null) {
headers.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), streamId);
}
}
if (ctx.channel().isWritable()) {
Http2Headers http2Headers = new DefaultHttp2Headers().status(httpResponseStatus.codeAsText()).add(headers);
Http2HeadersFrame http2HeadersFrame = new DefaultHttp2HeadersFrame(http2Headers, byteBuf == null);
ctx.channel().write(http2HeadersFrame);
if (byteBuf != null) {
Http2DataFrame http2DataFrame = new DefaultHttp2DataFrame(byteBuf, true);
ctx.channel().write(http2DataFrame);
}
ctx.channel().flush();
server.getResponseCounter().incrementAndGet();
} else {
logger.log(Level.WARNING, "channel is not writeable: " + ctx.channel());
}
}
/**
* Chunked response from a readable byte channel.
*
* @param chunkedInput chunked input
*/
@Override
public void write(ChunkedInput<ByteBuf> chunkedInput) {
Objects.requireNonNull(chunkedInput);
if (httpResponseStatus == null) {
httpResponseStatus = HttpResponseStatus.OK;
}
CharSequence contentType = headers.get(HttpHeaderNames.CONTENT_TYPE);
if (contentType == null) {
headers.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_OCTET_STREAM);
}
headers.add(HttpHeaderNames.TRANSFER_ENCODING, "chunked");
if (!headers.contains(HttpHeaderNames.DATE)) {
headers.add(HttpHeaderNames.DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC)));
}
headers.add(HttpHeaderNames.SERVER, ServerName.getServerName());
if (ctx.channel().isWritable()) {
Http2Headers http2Headers = new DefaultHttp2Headers().status(httpResponseStatus.codeAsText()).add(headers);
Http2HeadersFrame http2HeadersFrame = new DefaultHttp2HeadersFrame(http2Headers,false);
ctx.channel().write(http2HeadersFrame);
ChannelFuture channelFuture = ctx.channel().writeAndFlush(new HttpChunkedInput(chunkedInput));
if ("close".equalsIgnoreCase(serverRequest.getHeaders().get(HttpHeaderNames.CONNECTION)) &&
!headers.contains(HttpHeaderNames.CONNECTION)) {
channelFuture.addListener(ChannelFutureListener.CLOSE);
}
server.getResponseCounter().incrementAndGet();
} else {
logger.log(Level.WARNING, "channel is not writeable: " + ctx.channel());
}
}
}

View file

@ -1,50 +0,0 @@
package org.xbib.netty.http.server.transport;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http2.Http2Settings;
import io.netty.handler.codec.http2.HttpConversionUtil;
import io.netty.handler.ssl.SslHandler;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.api.ServerResponse;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Http2Transport extends BaseTransport {
private static final Logger logger = Logger.getLogger(Http2Transport.class.getName());
public Http2Transport(Server server) {
super(server);
}
@Override
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException {
HttpServerRequest serverRequest = new HttpServerRequest(fullHttpRequest,
(InetSocketAddress) ctx.channel().localAddress(),
(InetSocketAddress) ctx.channel().remoteAddress());
serverRequest.setSequenceId(sequenceId);
serverRequest.setRequestId(server.getRequestCounter().incrementAndGet());
serverRequest.setStreamId(fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text()));
SslHandler sslHandler = ctx.channel().pipeline().get(SslHandler.class);
if (sslHandler != null) {
serverRequest.setSession(sslHandler.engine().getSession());
}
ServerResponse serverResponse = new Http2ServerResponse(server, serverRequest, ctx);
if (acceptRequest(server.getServerConfig(), serverRequest, serverResponse)) {
serverRequest.handleParameters();
server.handle(serverRequest, serverResponse);
} else {
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE);
}
}
@Override
public void settingsReceived(ChannelHandlerContext ctx, Http2Settings http2Settings) {
logger.log(Level.FINER, "settings received, ignoring");
}
}

View file

@ -1,247 +0,0 @@
package org.xbib.netty.http.server.transport;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpUtil;
import org.xbib.net.Pair;
import org.xbib.net.PercentDecoder;
import org.xbib.net.QueryParameters;
import org.xbib.net.URL;
import org.xbib.netty.http.common.HttpParameters;
import org.xbib.netty.http.server.api.ServerRequest;
import javax.net.ssl.SSLSession;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The {@code HttpServerRequest} class encapsulates a single request.
* There must be a default constructor.
*/
public class HttpServerRequest implements ServerRequest {
private static final Logger logger = Logger.getLogger(HttpServerRequest.class.getName());
private static final String PATH_SEPARATOR = "/";
private final FullHttpRequest httpRequest;
private final InetSocketAddress localAddress;
private final InetSocketAddress remoteAddress;
private final Map<String, String> pathParameters;
private List<String> context;
private String contextPath;
private HttpParameters parameters;
private URL url;
private Integer sequenceId;
private Integer streamId;
private Long requestId;
private SSLSession sslSession;
public HttpServerRequest(FullHttpRequest fullHttpRequest) {
this( fullHttpRequest ,null, null);
}
public HttpServerRequest(FullHttpRequest fullHttpRequest,
InetSocketAddress localAddress,
InetSocketAddress remoteAddress) {
this.httpRequest = fullHttpRequest != null ? fullHttpRequest.retainedDuplicate() : null;
this.localAddress = localAddress;
this.remoteAddress = remoteAddress;
this.pathParameters = new LinkedHashMap<>();
}
void handleParameters() {
Charset charset = HttpUtil.getCharset(httpRequest, StandardCharsets.UTF_8);
this.url = URL.builder()
.charset(charset, CodingErrorAction.REPLACE)
.path(httpRequest.uri()) // creates path, query params, fragment
.build();
QueryParameters queryParameters = url.getQueryParams();
CharSequence mimeType = HttpUtil.getMimeType(httpRequest);
ByteBuf byteBuf = httpRequest.content();
if (byteBuf != null) {
if (httpRequest.method().equals(HttpMethod.POST)) {
String params;
// https://www.w3.org/TR/html4/interact/forms.html#h-17.13.4
if (mimeType != null && HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString().equals(mimeType.toString())) {
Charset htmlCharset = HttpUtil.getCharset(httpRequest, StandardCharsets.ISO_8859_1);
params = byteBuf.toString(htmlCharset).replace('+', ' ');
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER, "html form, charset = " + htmlCharset + " param body = " + params);
}
queryParameters.addPercentEncodedBody(params);
}
}
}
// copy to HTTP parameters but percent-decoded (looks very clumsy)
PercentDecoder percentDecoder = new PercentDecoder(charset.newDecoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE));
HttpParameters httpParameters = new HttpParameters(mimeType, charset);
for (Pair<String, String> pair : queryParameters) {
try {
httpParameters.addRaw(percentDecoder.decode(pair.getFirst()), percentDecoder.decode(pair.getSecond()));
} catch (Exception e) {
// does not happen
throw new IllegalArgumentException(pair.toString());
}
}
this.parameters = httpParameters;
}
@Override
public URL getURL() {
return url;
}
@Override
public void setContext(List<String> context) {
this.context = context;
this.contextPath = context != null ? PATH_SEPARATOR + String.join(PATH_SEPARATOR, context) : null;
}
@Override
public List<String> getContext() {
return context;
}
@Override
public String getContextPath() {
return contextPath;
}
@Override
public String getEffectiveRequestPath() {
String path = extractPath(getRequestURI());
String effective = contextPath != null && !PATH_SEPARATOR.equals(contextPath) && path.startsWith(contextPath) ?
path.substring(contextPath.length()) : path;
return effective.isEmpty() ? PATH_SEPARATOR : effective;
}
@Override
public void addPathParameter(String key, String value) throws IOException {
pathParameters.put(key, value);
parameters.addRaw(key, value);
}
@Override
public Map<String, String> getPathParameters() {
return pathParameters;
}
@Override
public HttpMethod getMethod() {
return httpRequest.method();
}
@Override
public HttpHeaders getHeaders() {
return httpRequest.headers();
}
@Override
public HttpParameters getParameters() {
return parameters;
}
@Override
public String getRequestURI() {
return httpRequest.uri();
}
public void setSequenceId(Integer sequenceId) {
this.sequenceId = sequenceId;
}
@Override
public Integer getSequenceId() {
return sequenceId;
}
public void setStreamId(Integer streamId) {
this.streamId = streamId;
}
@Override
public Integer getStreamId() {
return streamId;
}
public void setRequestId(Long requestId) {
this.requestId = requestId;
}
@Override
public Long getRequestId() {
return requestId;
}
public void setSession(SSLSession sslSession) {
this.sslSession = sslSession;
}
@Override
public SSLSession getSession() {
return sslSession;
}
@Override
public InetSocketAddress getLocalAddress() {
return localAddress;
}
@Override
public InetSocketAddress getRemoteAddress() {
return remoteAddress;
}
@Override
public ByteBuf getContent() {
return httpRequest.content();
}
@Override
public ByteBufInputStream getInputStream() {
return new ByteBufInputStream(getContent(), true);
}
public void release() {
httpRequest.release();
}
public String toString() {
return "ServerRequest[request=" + httpRequest + "]";
}
private static String extractPath(String uri) {
String path = uri;
int pos = uri.lastIndexOf('#');
path = pos >= 0 ? path.substring(0, pos) : path;
pos = uri.lastIndexOf('?');
path = pos >= 0 ? path.substring(0, pos) : path;
return path;
}
}

View file

@ -1,2 +1,2 @@
org.xbib.netty.http.server.Http1
org.xbib.netty.http.server.Http2
org.xbib.netty.http.server.protocol.http1.Http1
org.xbib.netty.http.server.protocol.http2.Http2

View file

@ -5,7 +5,6 @@ import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.HttpServerDomain;
import java.io.IOException;
@ -19,7 +18,7 @@ class BindExceptionTest {
@Test
void testDoubleServer() throws IOException {
HttpServerDomain domain = HttpServerDomain.builder(HttpAddress.http1("localhost", 8008))
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World"))
.singleEndpoint("/", (request, response) -> response.write("Hello World"))
.build();
Server server1 = Server.builder(domain).build();
Server server2 = Server.builder(domain).build();

View file

@ -13,26 +13,26 @@ import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.api.ServerRequest;
import org.xbib.netty.http.server.endpoint.HttpEndpoint;
import org.xbib.netty.http.server.endpoint.HttpEndpointResolver;
import org.xbib.netty.http.server.transport.HttpServerRequest;
import org.xbib.netty.http.server.HttpServerRequest;
@ExtendWith(NettyHttpTestExtension.class)
public class ContextURLTest {
@Test
void testServerPublishURL() throws Exception {
void testServerPublishURL() {
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
HttpEndpointResolver endpointResolver1 = HttpEndpointResolver.builder()
.addEndpoint(HttpEndpoint.builder().setPrefix("/one").setPath("/**").build())
.setDispatcher((endpoint, serverRequest, serverResponse) -> {})
.setDispatcher((serverRequest, serverResponse) -> {})
.build();
HttpEndpointResolver endpointResolver2 = HttpEndpointResolver.builder()
.addEndpoint(HttpEndpoint.builder().setPrefix("/two").setPath("/**").build())
.setDispatcher((endpoint, serverRequest, serverResponse) -> {})
.setDispatcher((serverRequest, serverResponse) -> {})
.build();
HttpEndpointResolver endpointResolver3 = HttpEndpointResolver.builder()
.addEndpoint(HttpEndpoint.builder().setPrefix("/three").setPath("/**").build())
.setDispatcher((endpoint, serverRequest, serverResponse) -> {})
.setDispatcher((serverRequest, serverResponse) -> {})
.build();
HttpServerDomain one = HttpServerDomain.builder(httpAddress, "domain.one:8008")
@ -48,27 +48,30 @@ public class ContextURLTest {
.addDomain(two)
.build();
URL url0 = server.getContextURL();
assertEquals("http://localhost:8008/", url0.toString());
DefaultFullHttpRequest fullHttpRequest1 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/one");
fullHttpRequest1.headers().add("host", "domain.one:8008");
ServerRequest serverRequest1 = new HttpServerRequest(fullHttpRequest1);
URL url1 = server.getContextURL(serverRequest1);
ServerRequest serverRequest1 = HttpServerRequest.builder()
.setHttpRequest(fullHttpRequest1)
.applyTo(server);
String contextPath1 = serverRequest1.getContextPath();
assertEquals("/one", contextPath1);
URL url1 = serverRequest1.getContextURL();
assertEquals("domain.one", url1.getHost());
assertEquals("/one/", url1.getPath());
DefaultFullHttpRequest fullHttpRequest2 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/two");
fullHttpRequest2.headers().add("host", "domain.two:8008");
ServerRequest serverRequest2 = new HttpServerRequest(fullHttpRequest2);
URL url2 = server.getContextURL(serverRequest2);
ServerRequest serverRequest2 = HttpServerRequest.builder().setHttpRequest(fullHttpRequest2).applyTo(server);
String contextPath2 = serverRequest2.getContextPath();
assertEquals("/two", contextPath2);
URL url2 = serverRequest2.getContextURL();
assertEquals("domain.two", url2.getHost());
assertEquals("/two/", url2.getPath());
DefaultFullHttpRequest fullHttpRequest3 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/three");
fullHttpRequest3.headers().add("host", "domain.two:8008");
ServerRequest serverRequest3 = new HttpServerRequest(fullHttpRequest3);
URL url3 = server.getContextURL(serverRequest3);
ServerRequest serverRequest3 = HttpServerRequest.builder().setHttpRequest(fullHttpRequest3).applyTo(server);
URL url3 = serverRequest3.getContextURL();
assertEquals("domain.two", url3.getHost());
assertEquals("/three/", url3.getPath());
}

View file

@ -9,7 +9,6 @@ import org.xbib.netty.http.client.api.Request;
import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.HttpServerDomain;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.api.ServerResponse;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.logging.Level;
@ -35,11 +34,13 @@ class MultiDomainSecureServerTest {
HttpServerDomain fl = HttpServerDomain.builder(httpAddress, "fl.hbz-nrw.de")
.setKeyCertChain(certInputStream)
.setKey(keyInputStream, null)
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello fl.hbz-nrw.de"))
.singleEndpoint("/", (request, response) ->
response.write("Hello fl.hbz-nrw.de"))
.build();
HttpServerDomain zfl2 = HttpServerDomain.builder(fl)
.setServerName("zfl2.hbz-nrw.de")
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello zfl2.hbz-nrw.de"))
.singleEndpoint("/", (request, response) ->
response.write( "Hello zfl2.hbz-nrw.de"))
.build();
Server server = Server.builder(fl)
.addDomain(zfl2)

View file

@ -8,7 +8,6 @@ import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.api.Request;
import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.HttpServerDomain;
import java.nio.charset.StandardCharsets;
import java.util.logging.Level;
@ -24,11 +23,13 @@ class MultiDomainServerTest {
void testServer() throws Exception {
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
HttpServerDomain fl = HttpServerDomain.builder(httpAddress, "fl.hbz-nrw.de")
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello fl.hbz-nrw.de"))
.singleEndpoint("/", (request, response) ->
response.write( "Hello fl.hbz-nrw.de"))
.build();
HttpServerDomain zfl2 = HttpServerDomain.builder(fl)
.setServerName("zfl2.hbz-nrw.de")
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello zfl2.hbz-nrw.de"))
.singleEndpoint("/", (request, response) ->
response.write("Hello zfl2.hbz-nrw.de"))
.build();
Server server = Server.builder(fl)
.addDomain(zfl2)

View file

@ -7,7 +7,6 @@ import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.HttpServerDomain;
import java.io.IOException;
@ -24,7 +23,8 @@ class ThreadLeakTest {
@Test
void testForLeaks() throws IOException {
HttpServerDomain domain = HttpServerDomain.builder(HttpAddress.http1("localhost", 8008))
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World"))
.singleEndpoint("/", (request, response) ->
response.write("Hello World"))
.build();
Server server = Server.builder(domain)
.setByteBufAllocator(UnpooledByteBufAllocator.DEFAULT)

View file

@ -29,9 +29,8 @@ class TransportLayerSecurityServerTest {
Server server = Server.builder(HttpServerDomain.builder(httpAddress)
.setSelfCert()
.singleEndpoint("/", (request, response) ->
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
.write(request.getContent().retain()))
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
.write(request.getContent().toString(StandardCharsets.UTF_8)))
.build())
.setTransportLayerSecurityProtocols("TLSv1.2")
.build();
@ -67,9 +66,8 @@ class TransportLayerSecurityServerTest {
Server server = Server.builder(HttpServerDomain.builder(httpAddress)
.setSelfCert()
.singleEndpoint("/", (request, response) ->
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
.write(request.getContent().retain()))
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
.write(request.getContent().toString(StandardCharsets.UTF_8)))
.build())
.setTransportLayerSecurityProtocols("TLSv1.3")
.build();

View file

@ -8,13 +8,11 @@ import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.api.Request;
import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.endpoint.HttpEndpoint;
import org.xbib.netty.http.server.endpoint.HttpEndpointResolver;
import org.xbib.netty.http.server.HttpServerDomain;
import org.xbib.netty.http.server.endpoint.service.FileService;
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@ -24,7 +22,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -40,8 +37,8 @@ class EndpointTest {
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
HttpEndpointResolver httpEndpointResolver = HttpEndpointResolver.builder()
.addEndpoint(HttpEndpoint.builder().setPath("/**").build())
.setDispatcher((endpoint, req, resp) -> {
logger.log(Level.FINE, "dispatching endpoint = " + endpoint +
.setDispatcher((req, resp) -> {
logger.log(Level.FINE, "dispatching endpoint = " + req.getEndpoint() +
" req = " + req + " req context path = " + req.getContextPath());
fileService.handle(req, resp);
})
@ -81,8 +78,8 @@ class EndpointTest {
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
HttpEndpointResolver httpEndpointResolver = HttpEndpointResolver.builder()
.addEndpoint(HttpEndpoint.builder().setPrefix("/").setPath("/**").build())
.setDispatcher((endpoint, req, resp) -> {
logger.log(Level.FINE, "dispatching endpoint = " + endpoint +
.setDispatcher((req, resp) -> {
logger.log(Level.FINE, "dispatching endpoint = " + req.getEndpoint() +
" req = " + req + " req context path = " + req.getContextPath());
fileService.handle(req, resp);
})
@ -124,8 +121,8 @@ class EndpointTest {
.addEndpoint(HttpEndpoint.builder().setPrefix("/static1").setPath("/**").build())
.addEndpoint(HttpEndpoint.builder().setPrefix("/static2").setPath("/**").build())
.addEndpoint(HttpEndpoint.builder().setPrefix("/static3").setPath("/**").build())
.setDispatcher((endpoint, req, resp) -> {
logger.log(Level.FINE, "dispatching endpoint = " + endpoint +
.setDispatcher(( req, resp) -> {
logger.log(Level.FINE, "dispatching endpoint = " + req.getEndpoint() +
" req = " + req + " req context path = " + req.getContextPath());
fileService.handle(req, resp);
})
@ -191,8 +188,8 @@ class EndpointTest {
.addEndpoint(HttpEndpoint.builder().setPrefix("/static1").setPath("/**").build())
.addEndpoint(HttpEndpoint.builder().setPrefix("/static2").setPath("/**").build())
.addEndpoint(HttpEndpoint.builder().setPrefix("/static3").setPath("/**").build())
.setDispatcher((endpoint, req, resp) -> {
logger.log(Level.FINE, "dispatching endpoint = " + endpoint + " req = " + req);
.setDispatcher((req, resp) -> {
logger.log(Level.FINE, "dispatching endpoint = " + req.getEndpoint() + " req = " + req);
fileService.handle(req, resp);
})
.build();
@ -274,22 +271,22 @@ class EndpointTest {
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
HttpEndpointResolver httpEndpointResolver1 = HttpEndpointResolver.builder()
.addEndpoint(HttpEndpoint.builder().setPrefix("/static1").setPath("/**").build())
.setDispatcher((endpoint, req, resp) -> {
logger.log(Level.FINE, "dispatching endpoint = " + endpoint + " context path = " + req.getContextPath());
.setDispatcher((req, resp) -> {
logger.log(Level.FINE, "dispatching endpoint = " + req.getEndpoint() + " context path = " + req.getContextPath());
fileService1.handle(req, resp);
})
.build();
HttpEndpointResolver httpEndpointResolver2 = HttpEndpointResolver.builder()
.addEndpoint(HttpEndpoint.builder().setPrefix("/static2").setPath("/**").build())
.setDispatcher((endpoint, req, resp) -> {
logger.log(Level.FINE, "dispatching endpoint = " + endpoint + " context path = " + req.getContextPath());
.setDispatcher((req, resp) -> {
logger.log(Level.FINE, "dispatching endpoint = " + req.getEndpoint() + " context path = " + req.getContextPath());
fileService2.handle(req, resp);
})
.build();
HttpEndpointResolver httpEndpointResolver3 = HttpEndpointResolver.builder()
.addEndpoint(HttpEndpoint.builder().setPrefix("/static3").setPath("/**").build())
.setDispatcher((endpoint, req, resp) -> {
logger.log(Level.FINE, "dispatching endpoint = " + endpoint + " context path = " + req.getContextPath());
.setDispatcher((req, resp) -> {
logger.log(Level.FINE, "dispatching endpoint = " + req.getEndpoint() + " context path = " + req.getContextPath());
fileService3.handle(req, resp);
})
.build();
@ -378,7 +375,7 @@ class EndpointTest {
}
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
.addEndpointResolver(endpointResolverBuilder
.setDispatcher((endpoint,req, resp) -> ServerResponse.write(resp, HttpResponseStatus.OK))
.setDispatcher((req, resp) -> resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush())
.build())
.build();
Server server = Server.builder(domain)
@ -419,7 +416,7 @@ class EndpointTest {
for (int i = 0; i < max; i++) {
domainBuilder.addEndpointResolver(endpointResolverBuilder.addEndpoint(HttpEndpoint.builder()
.setPath("/" + i + "/**").build())
.setDispatcher((endpoint,req, resp) -> ServerResponse.write(resp, HttpResponseStatus.OK))
.setDispatcher((req, resp) -> resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush())
.build());
}
Server server = Server.builder(domainBuilder.build())

View file

@ -23,9 +23,9 @@ import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.server.handler.http.HttpPipelinedRequest;
import org.xbib.netty.http.server.handler.http.HttpPipelinedResponse;
import org.xbib.netty.http.server.handler.http.HttpPipeliningHandler;
import org.xbib.netty.http.server.protocol.http1.HttpPipelinedRequest;
import org.xbib.netty.http.server.protocol.http1.HttpPipelinedResponse;
import org.xbib.netty.http.server.protocol.http1.HttpPipeliningHandler;
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
import java.nio.channels.ClosedChannelException;

View file

@ -12,7 +12,6 @@ import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.common.HttpResponse;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.HttpServerDomain;
import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
import java.nio.charset.StandardCharsets;
@ -35,8 +34,8 @@ class CleartextTest {
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
.singleEndpoint("/**", (request, response) ->
ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
request.getContent().toString(StandardCharsets.UTF_8)))
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
.write(request.getContent().toString(StandardCharsets.UTF_8)))
.build();
Server server = Server.builder(domain).build();
server.accept();
@ -69,8 +68,8 @@ class CleartextTest {
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
.singleEndpoint("/**", (request, response) ->
ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
request.getContent().toString(StandardCharsets.UTF_8)))
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
.write(request.getContent().toString(StandardCharsets.UTF_8)))
.build();
Server server = Server.builder(domain).build();
server.accept();
@ -114,8 +113,8 @@ class CleartextTest {
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
.singleEndpoint("/**", (request, response) ->
ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
request.getContent().toString(StandardCharsets.UTF_8)))
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
.write(request.getContent().toString(StandardCharsets.UTF_8)))
.build();
Server server = Server.builder(domain).build();
server.accept();
@ -167,8 +166,6 @@ class CleartextTest {
server.shutdownGracefully(20L, TimeUnit.SECONDS);
client.shutdownGracefully(20L, TimeUnit.SECONDS);
}
logger.log(Level.INFO, "server requests = " + server.getRequestCounter() +
" server responses = " + server.getResponseCounter());
logger.log(Level.INFO, "client requests = " + client.getRequestCounter() +
" client responses = " + client.getResponseCounter());
logger.log(Level.INFO, "expected=" + (threads * loop) + " counter=" + counter.get());

View file

@ -13,8 +13,8 @@ import org.xbib.netty.http.common.HttpResponse;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.HttpServerDomain;
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@ -35,8 +35,7 @@ class EncryptedTest {
Server server = Server.builder(HttpServerDomain.builder(httpAddress)
.setSelfCert()
.singleEndpoint("/", (request, response) ->
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
.write(request.getContent().retain()))
.build())
.build();
@ -66,9 +65,8 @@ class EncryptedTest {
Server server = Server.builder(HttpServerDomain.builder(httpAddress)
.setSelfCert()
.singleEndpoint("/", (request, response) ->
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
.write(request.getContent().retain()))
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
.write(request.getContent().toString(StandardCharsets.UTF_8)))
.build())
.build();
server.accept();
@ -110,9 +108,8 @@ class EncryptedTest {
Server server = Server.builder(HttpServerDomain.builder(httpAddress)
.setSelfCert()
.singleEndpoint("/", (request, response) ->
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
.write(request.getContent().retain()))
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
.write(request.getContent().toString(StandardCharsets.UTF_8)))
.build())
.build();
server.accept();

View file

@ -37,7 +37,7 @@ class FlushTest {
.singleEndpoint("/flush", "/**", (req, resp) -> {
HttpParameters parameters = req.getParameters();
logger.log(Level.INFO, "got request " + parameters.toString() + ", sending 302 Found");
resp.withStatus(HttpResponseStatus.FOUND).flush();
resp.getBuilder().setStatus(HttpResponseStatus.FOUND).build().flush();
})
.build();
Server server = Server.builder(domain)

View file

@ -17,7 +17,6 @@ import org.xbib.netty.http.common.HttpParameters;
import org.xbib.netty.http.common.HttpResponse;
import org.xbib.netty.http.server.HttpServerDomain;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicBoolean;
@ -39,7 +38,7 @@ class MimeUploadTest {
logger.log(Level.INFO, "got request, headers = " + req.getHeaders() +
" params = " + parameters.toString() +
" body = " + req.getContent().toString(StandardCharsets.UTF_8));
ServerResponse.write(resp, HttpResponseStatus.OK);
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
}, "POST")
.build();
Server server = Server.builder(domain)

View file

@ -42,7 +42,7 @@ class PostTest {
if ("Jörg".equals(parameters.getFirst("name"))) {
success3.set(true);
}
ServerResponse.write(resp, HttpResponseStatus.OK);
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
}, "POST")
.build();
Server server = Server.builder(domain)
@ -92,7 +92,7 @@ class PostTest {
if ("Jörg".equals(parameters.getFirst("name"))) {
success3.set(true);
}
ServerResponse.write(resp, HttpResponseStatus.OK);
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
}, "POST")
.build();
Server server = Server.builder(domain)
@ -146,7 +146,7 @@ class PostTest {
if ("my value".equals(parameters.getFirst("my param"))) {
success4.set(true);
}
ServerResponse.write(resp, HttpResponseStatus.OK);
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
}, "POST")
.build();
Server server = Server.builder(domain)
@ -203,7 +203,7 @@ class PostTest {
if ("my value".equals(parameters.getFirst("my param"))) {
success4.set(true);
}
ServerResponse.write(resp, HttpResponseStatus.OK);
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
}, "POST")
.build();
Server server = Server.builder(domain)
@ -257,7 +257,7 @@ class PostTest {
if ("bÿc".equals(parameters.getFirst("a"))) {
success2.set(true);
}
ServerResponse.write(resp, HttpResponseStatus.OK);
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
}, "POST")
.build();
Server server = Server.builder(domain)

View file

@ -36,7 +36,7 @@ class PutTest {
.singleEndpoint("/put", "/**", (req, resp) -> {
logger.log(Level.INFO, "got request " +
req.getContent().toString(StandardCharsets.UTF_8));
ServerResponse.write(resp, HttpResponseStatus.OK);
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
success1.set(true);
}, "PUT")
.build();
@ -84,7 +84,7 @@ class PutTest {
.singleEndpoint("/put", "/**", (req, resp) -> {
logger.log(Level.INFO, "got request, length = " +
req.getContent().readableBytes());
ServerResponse.write(resp, HttpResponseStatus.OK);
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
success1.set(true);
}, "PUT")
.build();

View file

@ -31,8 +31,7 @@ class StreamTest {
assertEquals("my body parameter", content);
ByteBufOutputStream outputStream = response.getOutputStream();
outputStream.writeBytes("Hello World");
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
.write(outputStream);
})
.build();

View file

@ -10,10 +10,8 @@ import org.xbib.netty.http.client.api.ClientTransport;
import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.common.HttpResponse;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.HttpServerDomain;
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -34,8 +32,8 @@ class CleartextTest {
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
.singleEndpoint("/", (request, response) ->
ServerResponse.write(response, HttpResponseStatus.OK, "text.plain",
request.getContent().toString(StandardCharsets.UTF_8)))
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
.write(request.getContent().toString(StandardCharsets.UTF_8)))
.build();
Server server = Server.builder(domain)
.build();
@ -79,9 +77,8 @@ class CleartextTest {
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
.singleEndpoint("/", (request, response) ->
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
.write(request.getContent().retain()))
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
.write(request.getContent().toString(StandardCharsets.UTF_8)))
.build();
Server server = Server.builder(domain)
.build();
@ -131,8 +128,8 @@ class CleartextTest {
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
.singleEndpoint("/**", (request, response) ->
ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
request.getContent().toString(StandardCharsets.UTF_8)))
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
.write(request.getContent().toString(StandardCharsets.UTF_8)))
.build();
Server server = Server.builder(domain).build();
server.accept();
@ -186,8 +183,6 @@ class CleartextTest {
}
logger.log(Level.INFO, "client requests = " + client.getRequestCounter() +
" client responses = " + client.getResponseCounter());
logger.log(Level.INFO, "server requests = " + server.getRequestCounter() +
" server responses = " + server.getResponseCounter());
logger.log(Level.INFO, "expected=" + (threads * loop) + " counter=" + counter.get());
assertEquals(threads * loop , counter.get());
}
@ -200,8 +195,8 @@ class CleartextTest {
AtomicInteger counter1 = new AtomicInteger();
HttpServerDomain domain1 = HttpServerDomain.builder(httpAddress1)
.singleEndpoint("/", (request, response) -> {
ServerResponse.write(response, HttpResponseStatus.OK, "text.plain",
request.getContent().toString(StandardCharsets.UTF_8));
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
.write(request.getContent().toString(StandardCharsets.UTF_8));
counter1.incrementAndGet();
})
.build();
@ -212,8 +207,8 @@ class CleartextTest {
AtomicInteger counter2 = new AtomicInteger();
HttpServerDomain domain2 = HttpServerDomain.builder(httpAddress2)
.singleEndpoint("/", (request, response) -> {
ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
request.getContent().toString(StandardCharsets.UTF_8));
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
.write(request.getContent().toString(StandardCharsets.UTF_8));
counter2.incrementAndGet();
})
.build();
@ -273,10 +268,6 @@ class CleartextTest {
server2.shutdownGracefully();
client.shutdownGracefully();
}
logger.log(Level.INFO, "server1 requests = " + server1.getRequestCounter() +
" server1 responses = " + server1.getResponseCounter());
logger.log(Level.INFO, "server2 requests = " + server1.getRequestCounter() +
" server2 responses = " + server1.getResponseCounter());
logger.log(Level.INFO, "client requests = " + client.getRequestCounter() +
" client responses = " + client.getResponseCounter());
logger.log(Level.INFO, "counter1=" + counter1.get() + " counter2=" + counter2.get());

View file

@ -12,7 +12,6 @@ import org.xbib.netty.http.common.HttpResponse;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.HttpServerDomain;
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -34,9 +33,8 @@ class EncryptedTest {
Server server = Server.builder(HttpServerDomain.builder(httpAddress)
.setSelfCert()
.singleEndpoint("/", (request, response) ->
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
.write(request.getContent().retain()))
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
.write(request.getContent().toString(StandardCharsets.UTF_8)))
.build())
.build();
server.accept();
@ -75,9 +73,8 @@ class EncryptedTest {
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
.setSelfCert()
.singleEndpoint("/", (request, response) ->
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
.write(request.getContent().retain()))
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
.write(request.getContent().toString(StandardCharsets.UTF_8)))
.build();
Server server = Server.builder(domain)
.build();
@ -129,10 +126,8 @@ class EncryptedTest {
Server server = Server.builder(HttpServerDomain.builder(httpAddress)
.setSelfCert()
.singleEndpoint("/", (request, response) ->
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
.write(request.getContent().retain())
)
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
.write(request.getContent().toString(StandardCharsets.UTF_8)))
.build())
.build();
server.accept();

View file

@ -34,7 +34,7 @@ class FlushTest {
.singleEndpoint("/flush", "/**", (req, resp) -> {
HttpParameters parameters = req.getParameters();
logger.log(Level.INFO, "got request " + parameters.toString() + ", sending 302 Found");
resp.withStatus(HttpResponseStatus.FOUND).flush();
resp.getBuilder().setStatus(HttpResponseStatus.FOUND).build().flush();
})
.build();
Server server = Server.builder(domain)

View file

@ -10,7 +10,6 @@ import org.xbib.netty.http.client.api.ClientTransport;
import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.HttpServerDomain;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
import java.util.concurrent.atomic.AtomicInteger;
import io.netty.handler.codec.http.HttpResponseStatus;
@ -22,9 +21,7 @@ class MixedProtocolTest {
void testHttp1ClientHttp2Server() throws Exception {
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
.singleEndpoint("/", (request, response) -> {
ServerResponse.write(response, HttpResponseStatus.OK);
})
.singleEndpoint("/", (request, response) -> response.getBuilder().setStatus(HttpResponseStatus.OK).build().flush())
.build();
Server server = Server.builder(domain)
.build();
@ -56,9 +53,8 @@ class MixedProtocolTest {
void testHttp2ClientHttp1Server() throws Exception {
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
.singleEndpoint("/", (request, response) -> {
ServerResponse.write(response, HttpResponseStatus.OK);
})
.singleEndpoint("/", (request, response) ->
response.getBuilder().setStatus(HttpResponseStatus.OK).build().flush())
.build();
Server server = Server.builder(domain)
.build();
@ -94,9 +90,9 @@ class MixedProtocolTest {
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
.setSelfCert()
.singleEndpoint("/", (request, response) -> {
ServerResponse.write(response, HttpResponseStatus.OK);
})
.singleEndpoint("/", (request, response) ->
response.getBuilder().setStatus(HttpResponseStatus.OK).build().flush()
)
.build();
Server server = Server.builder(domain)
//.enableDebug()

View file

@ -11,7 +11,6 @@ import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.common.HttpParameters;
import org.xbib.netty.http.common.HttpResponse;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.HttpServerDomain;
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
@ -43,7 +42,7 @@ class PostTest {
if ("Jörg".equals(parameters.getFirst("name"))) {
success3.set(true);
}
ServerResponse.write(resp, HttpResponseStatus.OK);
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
}, "POST")
.build();
Server server = Server.builder(domain)
@ -93,7 +92,7 @@ class PostTest {
if ("Jörg".equals(parameters.getFirst("name"))) {
success3.set(true);
}
ServerResponse.write(resp, HttpResponseStatus.OK);
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
}, "POST")
.build();
Server server = Server.builder(domain)

View file

@ -10,7 +10,6 @@ import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.common.HttpResponse;
import org.xbib.netty.http.server.HttpServerDomain;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.api.ServerResponse;
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicBoolean;
@ -35,7 +34,7 @@ class PutTest {
.singleEndpoint("/put", "/**", (req, resp) -> {
logger.log(Level.INFO, "got request " +
req.getContent().toString(StandardCharsets.UTF_8));
ServerResponse.write(resp, HttpResponseStatus.OK);
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
success1.set(true);
}, "PUT")
.build();
@ -84,7 +83,7 @@ class PutTest {
.singleEndpoint("/put", "/**", (req, resp) -> {
logger.log(Level.INFO, "got request, length = " +
req.getContent().readableBytes());
ServerResponse.write(resp, HttpResponseStatus.OK);
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
success1.set(true);
}, "PUT")
.build();

View file

@ -28,8 +28,9 @@ class StreamTest {
assertEquals("my body parameter", content);
ByteBufOutputStream outputStream = response.getOutputStream();
outputStream.writeBytes("Hello World");
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
response.getBuilder().setStatus(HttpResponseStatus.OK)
.setContentType("text/plain")
.build()
.write(outputStream);
})
.build();