introduce builders for server requests und responses, add context info on server requests
This commit is contained in:
parent
fb062ebf04
commit
849a77aeec
59 changed files with 1573 additions and 1223 deletions
|
@ -1,6 +1,6 @@
|
||||||
group = org.xbib
|
group = org.xbib
|
||||||
name = netty-http
|
name = netty-http
|
||||||
version = 4.1.51.2
|
version = 4.1.51.3
|
||||||
|
|
||||||
gradle.wrapper.version = 6.4.1
|
gradle.wrapper.version = 6.4.1
|
||||||
netty.version = 4.1.51.Final
|
netty.version = 4.1.51.Final
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.netty.http.server.util;
|
package org.xbib.netty.http.common.util;
|
||||||
|
|
||||||
public class HtmlUtils {
|
public class HtmlUtils {
|
||||||
|
|
|
@ -18,7 +18,5 @@ public interface Domain<R extends EndpointResolver<?>> {
|
||||||
|
|
||||||
Collection<? extends X509Certificate> getCertificateChain();
|
Collection<? extends X509Certificate> getCertificateChain();
|
||||||
|
|
||||||
String findContextPathOf(ServerRequest serverRequest) throws IOException;
|
void handle(ServerRequest.Builder serverRequestBuilder, ServerResponse.Builder serverResponseBuilder) throws IOException;
|
||||||
|
|
||||||
void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,9 @@ public interface Endpoint<D extends EndpointDescriptor> {
|
||||||
|
|
||||||
boolean matches(D descriptor);
|
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;
|
void before(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -1,16 +1,14 @@
|
||||||
package org.xbib.netty.http.server.api;
|
package org.xbib.netty.http.server.api;
|
||||||
|
|
||||||
|
import org.xbib.netty.http.common.HttpMethod;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
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,
|
void handle(E matchingEndpoint,
|
||||||
ServerRequest serverRequest) throws IOException;
|
|
||||||
|
|
||||||
void handle(List<E> matchingEndpoints,
|
|
||||||
ServerRequest serverRequest,
|
ServerRequest serverRequest,
|
||||||
ServerResponse serverResponse) throws IOException;
|
ServerResponse serverResponse) throws IOException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,23 +6,23 @@ import io.netty.handler.codec.http.HttpHeaders;
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
import io.netty.handler.codec.http.HttpMethod;
|
||||||
import org.xbib.net.URL;
|
import org.xbib.net.URL;
|
||||||
import org.xbib.netty.http.common.HttpParameters;
|
import org.xbib.netty.http.common.HttpParameters;
|
||||||
|
|
||||||
import javax.net.ssl.SSLSession;
|
import javax.net.ssl.SSLSession;
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public interface ServerRequest {
|
public interface ServerRequest {
|
||||||
|
|
||||||
|
Builder getBuilder();
|
||||||
|
|
||||||
|
InetSocketAddress getLocalAddress();
|
||||||
|
|
||||||
|
InetSocketAddress getRemoteAddress();
|
||||||
|
|
||||||
URL getURL();
|
URL getURL();
|
||||||
|
|
||||||
void setContext(List<String> context);
|
|
||||||
|
|
||||||
List<String> getContext();
|
List<String> getContext();
|
||||||
|
|
||||||
void addPathParameter(String key, String value) throws IOException;
|
|
||||||
|
|
||||||
Map<String, String> getPathParameters();
|
Map<String, String> getPathParameters();
|
||||||
|
|
||||||
String getRequestURI();
|
String getRequestURI();
|
||||||
|
@ -49,7 +49,40 @@ public interface ServerRequest {
|
||||||
|
|
||||||
SSLSession getSession();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,38 +2,34 @@ package org.xbib.netty.http.server.api;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufOutputStream;
|
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.codec.http.HttpResponseStatus;
|
||||||
import io.netty.handler.stream.ChunkedInput;
|
import io.netty.handler.stream.ChunkedInput;
|
||||||
import org.xbib.netty.http.common.cookie.Cookie;
|
import org.xbib.netty.http.common.cookie.Cookie;
|
||||||
|
import java.io.Flushable;
|
||||||
|
import java.io.IOException;
|
||||||
import java.nio.CharBuffer;
|
import java.nio.CharBuffer;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP server response.
|
* 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);
|
Long getResponseId();
|
||||||
|
|
||||||
ServerResponse withContentType(String contentType);
|
|
||||||
|
|
||||||
ServerResponse withCharset(Charset charset);
|
|
||||||
|
|
||||||
ServerResponse withCookie(Cookie cookie);
|
|
||||||
|
|
||||||
ByteBufOutputStream getOutputStream();
|
ByteBufOutputStream getOutputStream();
|
||||||
|
|
||||||
void flush();
|
void flush() throws IOException;
|
||||||
|
|
||||||
|
void write(String content);
|
||||||
|
|
||||||
|
void write(CharBuffer charBuffer, Charset charset);
|
||||||
|
|
||||||
void write(byte[] bytes);
|
void write(byte[] bytes);
|
||||||
|
|
||||||
|
@ -43,56 +39,30 @@ public interface ServerResponse {
|
||||||
|
|
||||||
void write(ChunkedInput<ByteBuf> chunkedInput);
|
void write(ChunkedInput<ByteBuf> chunkedInput);
|
||||||
|
|
||||||
static void write(ServerResponse serverResponse, int status) {
|
interface Builder {
|
||||||
write(serverResponse, HttpResponseStatus.valueOf(status));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void write(ServerResponse serverResponse, HttpResponseStatus status) {
|
Builder setStatus(HttpResponseStatus httpResponseStatus);
|
||||||
write(serverResponse, status, "application/octet-stream", EMPTY_STRING);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
Builder setContentType(CharSequence contentType);
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void write(ServerResponse serverResponse, String text) {
|
Builder setCharset(Charset charset);
|
||||||
write(serverResponse, HttpResponseStatus.OK, "text/plain", text);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void write(ServerResponse serverResponse, HttpResponseStatus status, String contentType, String text) {
|
Builder setHeader(CharSequence name, String value);
|
||||||
ByteBuf byteBuf = ByteBufUtil.writeUtf8(serverResponse.getChannelHandlerContext().alloc(), text);
|
|
||||||
serverResponse.withStatus(status)
|
|
||||||
.withContentType(contentType)
|
|
||||||
.withCharset(StandardCharsets.UTF_8)
|
|
||||||
.write(byteBuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void write(ServerResponse serverResponse, HttpResponseStatus status, String contentType, ByteBuf byteBuf) {
|
Builder setTrailingHeader(CharSequence name, String value);
|
||||||
serverResponse.withStatus(status)
|
|
||||||
.withContentType(contentType)
|
|
||||||
.withCharset(StandardCharsets.UTF_8)
|
|
||||||
.write(byteBuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void write(ServerResponse serverResponse,
|
Builder addCookie(Cookie cookie);
|
||||||
HttpResponseStatus status, String contentType, String text, Charset charset) {
|
|
||||||
write(serverResponse, status, contentType, CharBuffer.allocate(text.length()).append(text), charset);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void write(ServerResponse serverResponse, HttpResponseStatus status, String contentType,
|
Builder shouldClose(boolean shouldClose);
|
||||||
CharBuffer charBuffer, Charset charset) {
|
|
||||||
ByteBuf byteBuf = ByteBufUtil.encodeString(serverResponse.getChannelHandlerContext().alloc(), charBuffer, charset);
|
|
||||||
serverResponse.withStatus(status)
|
|
||||||
.withContentType(contentType)
|
|
||||||
.withCharset(charset)
|
|
||||||
.write(byteBuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
String EMPTY_STRING = "";
|
Builder shouldAddServerName(boolean shouldAddServerName);
|
||||||
|
|
||||||
|
Builder setSequenceId(Integer sequenceId);
|
||||||
|
|
||||||
|
Builder setStreamId(Integer streamId);
|
||||||
|
|
||||||
|
Builder setResponseId(Long responseId);
|
||||||
|
|
||||||
|
ServerResponse build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import org.xbib.netty.http.server.Http1;
|
import org.xbib.netty.http.server.protocol.http1.Http1;
|
||||||
import org.xbib.netty.http.server.Http2;
|
import org.xbib.netty.http.server.protocol.http2.Http2;
|
||||||
|
|
||||||
module org.xbib.netty.http.server {
|
module org.xbib.netty.http.server {
|
||||||
uses org.xbib.netty.http.server.api.security.ServerCertificateProvider;
|
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;
|
||||||
exports org.xbib.netty.http.server.endpoint.service;
|
exports org.xbib.netty.http.server.endpoint.service;
|
||||||
exports org.xbib.netty.http.server.handler;
|
exports org.xbib.netty.http.server.handler;
|
||||||
exports org.xbib.netty.http.server.handler.http;
|
exports org.xbib.netty.http.server.protocol.http1;
|
||||||
exports org.xbib.netty.http.server.handler.http2;
|
exports org.xbib.netty.http.server.protocol.http2;
|
||||||
exports org.xbib.netty.http.server.handler.stream;
|
|
||||||
exports org.xbib.netty.http.server.transport;
|
|
||||||
exports org.xbib.netty.http.server.util;
|
exports org.xbib.netty.http.server.util;
|
||||||
requires transitive org.xbib.netty.http.server.api;
|
requires transitive org.xbib.netty.http.server.api;
|
||||||
requires java.logging;
|
requires java.logging;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,19 +45,19 @@ public class DefaultServerConfig implements ServerConfig {
|
||||||
int PARENT_THREAD_COUNT = 0;
|
int PARENT_THREAD_COUNT = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Child thread count. Let Netty decide.
|
* Let Netty decide about child thread count.
|
||||||
*/
|
*/
|
||||||
int CHILD_THREAD_COUNT = 0;
|
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.
|
* Default for SO_REUSEADDR.
|
||||||
|
|
|
@ -7,7 +7,9 @@ import io.netty.handler.ssl.CipherSuiteFilter;
|
||||||
import io.netty.handler.ssl.SslContext;
|
import io.netty.handler.ssl.SslContext;
|
||||||
import io.netty.handler.ssl.SslContextBuilder;
|
import io.netty.handler.ssl.SslContextBuilder;
|
||||||
import io.netty.handler.ssl.SslProvider;
|
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.HttpAddress;
|
||||||
|
import org.xbib.netty.http.common.HttpMethod;
|
||||||
import org.xbib.netty.http.server.api.Domain;
|
import org.xbib.netty.http.server.api.Domain;
|
||||||
import org.xbib.netty.http.server.api.EndpointResolver;
|
import org.xbib.netty.http.server.api.EndpointResolver;
|
||||||
import org.xbib.netty.http.server.api.security.ServerCertificateProvider;
|
import org.xbib.netty.http.server.api.security.ServerCertificateProvider;
|
||||||
|
@ -36,21 +38,14 @@ import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.ServiceLoader;
|
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.
|
* The {@code HttpServerDomain} class represents a virtual server with a name.
|
||||||
*/
|
*/
|
||||||
public class HttpServerDomain implements Domain<HttpEndpointResolver> {
|
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 String name;
|
||||||
|
|
||||||
private final HttpAddress httpAddress;
|
private final HttpAddress httpAddress;
|
||||||
|
@ -137,40 +132,41 @@ public class HttpServerDomain implements Domain<HttpEndpointResolver> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Evaluate the context path of a given request.
|
* Handle server requests by resolving and handling.
|
||||||
* The request is not dispatched.
|
* @param serverRequestBuilder the server request
|
||||||
* URI request parameters are evaluated.
|
* @param serverResponseBuilder the server response
|
||||||
* @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
|
|
||||||
* @throws IOException if handling server request fails
|
* @throws IOException if handling server request fails
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
public void handle(ServerRequest.Builder serverRequestBuilder,
|
||||||
Map.Entry<HttpEndpointResolver, List<HttpEndpoint>> resolved = resolve(serverRequest);
|
ServerResponse.Builder serverResponseBuilder) throws IOException {
|
||||||
if (resolved != null) {
|
String path = extractPath(serverRequestBuilder.getRequestURI());
|
||||||
resolved.getKey().handle(resolved.getValue(), serverRequest, serverResponse);
|
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 {
|
} else {
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND,
|
if (serverResponseBuilder != null) {
|
||||||
"text/plain", "No endpoint found to match request");
|
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 + ")";
|
return name + " (" + httpAddress + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static String extractPath(String uri) {
|
||||||
* Just resolve a server request to a matching endpoint resolver with endpoints matched.
|
String path = uri;
|
||||||
* @param serverRequest the server request
|
int pos = uri.lastIndexOf('#');
|
||||||
* @return the endpoint resolver together with the matching endpoints
|
path = pos >= 0 ? path.substring(0, pos) : path;
|
||||||
*/
|
pos = uri.lastIndexOf('?');
|
||||||
private Map.Entry<HttpEndpointResolver, List<HttpEndpoint>> resolve(ServerRequest serverRequest) {
|
path = pos >= 0 ? path.substring(0, pos) : path;
|
||||||
for (HttpEndpointResolver httpEndpointResolver : httpEndpointResolvers) {
|
return path;
|
||||||
List<HttpEndpoint> matchingEndpoints = httpEndpointResolver.matchingEndpointsFor(serverRequest);
|
|
||||||
if (!matchingEndpoints.isEmpty()) {
|
|
||||||
return Map.entry(httpEndpointResolver, matchingEndpoints);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
@ -323,7 +313,6 @@ public class HttpServerDomain implements Domain<HttpEndpointResolver> {
|
||||||
serverCertificateProvider.prepare(serverName);
|
serverCertificateProvider.prepare(serverName);
|
||||||
setKeyCertChain(serverCertificateProvider.getCertificateChain());
|
setKeyCertChain(serverCertificateProvider.getCertificateChain());
|
||||||
setKey(serverCertificateProvider.getPrivateKey(), serverCertificateProvider.getKeyPassword());
|
setKey(serverCertificateProvider.getPrivateKey(), serverCertificateProvider.getKeyPassword());
|
||||||
logger.log(Level.INFO, "self signed certificate installed");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (keyCertChain == null) {
|
if (keyCertChain == null) {
|
||||||
|
@ -346,7 +335,7 @@ public class HttpServerDomain implements Domain<HttpEndpointResolver> {
|
||||||
.addEndpoint(HttpEndpoint.builder()
|
.addEndpoint(HttpEndpoint.builder()
|
||||||
.setPath(path)
|
.setPath(path)
|
||||||
.build())
|
.build())
|
||||||
.setDispatcher((endpoint, req, resp) -> filter.handle(req, resp))
|
.setDispatcher(filter::handle)
|
||||||
.build());
|
.build());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -360,7 +349,7 @@ public class HttpServerDomain implements Domain<HttpEndpointResolver> {
|
||||||
.setPrefix(prefix)
|
.setPrefix(prefix)
|
||||||
.setPath(path)
|
.setPath(path)
|
||||||
.build())
|
.build())
|
||||||
.setDispatcher((endpoint, req, resp) -> filter.handle(req, resp))
|
.setDispatcher(filter::handle)
|
||||||
.build());
|
.build());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -376,7 +365,7 @@ public class HttpServerDomain implements Domain<HttpEndpointResolver> {
|
||||||
.setPath(path)
|
.setPath(path)
|
||||||
.setMethods(Arrays.asList(methods))
|
.setMethods(Arrays.asList(methods))
|
||||||
.build())
|
.build())
|
||||||
.setDispatcher((endpoint, req, resp) -> filter.handle(req, resp))
|
.setDispatcher(filter::handle)
|
||||||
.build());
|
.build());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import io.netty.channel.WriteBufferWaterMark;
|
||||||
import io.netty.channel.nio.NioEventLoopGroup;
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
import io.netty.channel.socket.ServerSocketChannel;
|
import io.netty.channel.socket.ServerSocketChannel;
|
||||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
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.codec.http.HttpVersion;
|
||||||
import io.netty.handler.logging.LoggingHandler;
|
import io.netty.handler.logging.LoggingHandler;
|
||||||
import io.netty.handler.ssl.SslContext;
|
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.api.ServerTransport;
|
||||||
import org.xbib.netty.http.server.endpoint.HttpEndpointResolver;
|
import org.xbib.netty.http.server.endpoint.HttpEndpointResolver;
|
||||||
import org.xbib.netty.http.server.security.CertificateUtils;
|
import org.xbib.netty.http.server.security.CertificateUtils;
|
||||||
import org.xbib.netty.http.server.transport.HttpServerRequest;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.security.cert.CertificateExpiredException;
|
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 DefaultServerConfig serverConfig;
|
||||||
|
|
||||||
private final EventLoopGroup parentEventLoopGroup;
|
private final EventLoopGroup parentEventLoopGroup;
|
||||||
|
@ -81,7 +77,8 @@ public final class Server implements AutoCloseable {
|
||||||
private final EventLoopGroup childEventLoopGroup;
|
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;
|
private final BlockingThreadPoolExecutor executor;
|
||||||
|
|
||||||
|
@ -91,6 +88,10 @@ public final class Server implements AutoCloseable {
|
||||||
|
|
||||||
private final List<ServerProtocolProvider<HttpChannelInitializer, ServerTransport>> protocolProviders;
|
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.
|
* Create a new HTTP server.
|
||||||
*
|
*
|
||||||
|
@ -196,14 +197,40 @@ public final class Server implements AutoCloseable {
|
||||||
return channelFuture;
|
return channelFuture;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public AtomicLong getRequestCounter() {
|
||||||
* Returns the domain for the given server request.
|
return requestCounter;
|
||||||
*
|
}
|
||||||
* @param serverRequest the server request
|
|
||||||
* @return the domain
|
public AtomicLong getResponseCounter() {
|
||||||
*/
|
return responseCounter;
|
||||||
public Domain<? extends EndpointResolver<?>> getDomain(ServerRequest serverRequest) {
|
}
|
||||||
return getDomain(getBaseURL(serverRequest));
|
|
||||||
|
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 serverConfig.getDomain(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void handle(ServerRequest.Builder serverRequestBuilder,
|
||||||
* Return the base URL regarding to a server request.
|
ServerResponse.Builder serverResponseBuilder) throws IOException {
|
||||||
* The base URL depends on the host and port defined in a reqeust,
|
URL baseURL = getBaseURL(serverRequestBuilder.getHeaders());
|
||||||
* if no request is defined, the bind URL is taken.
|
serverRequestBuilder.setBaseURL(baseURL);
|
||||||
* @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);
|
|
||||||
Domain<? extends EndpointResolver<?>> domain = getDomain(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) {
|
if (executor != null) {
|
||||||
executor.submit(() -> {
|
executor.submit(() -> {
|
||||||
try {
|
try {
|
||||||
domain.handle(serverRequest, serverResponse);
|
domain.handle(serverRequestBuilder, serverResponseBuilder);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
executor.afterExecute(null, e);
|
executor.afterExecute(null, e);
|
||||||
} finally {
|
} finally {
|
||||||
serverRequest.release();
|
serverRequestBuilder.release();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
domain.handle(serverRequest, serverResponse);
|
domain.handle(serverRequestBuilder, serverResponseBuilder);
|
||||||
} finally {
|
} finally {
|
||||||
serverRequest.release();
|
serverRequestBuilder.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public AtomicLong getRequestCounter() {
|
|
||||||
return requestCounter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AtomicLong getResponseCounter() {
|
|
||||||
return responseCounter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServerTransport newTransport(HttpVersion httpVersion) {
|
public ServerTransport newTransport(HttpVersion httpVersion) {
|
||||||
for (ServerProtocolProvider<HttpChannelInitializer, ServerTransport> protocolProvider : protocolProviders) {
|
for (ServerProtocolProvider<HttpChannelInitializer, ServerTransport> protocolProvider : protocolProviders) {
|
||||||
if (protocolProvider.supportsMajorVersion(httpVersion.majorVersion())) {
|
if (protocolProvider.supportsMajorVersion(httpVersion.majorVersion())) {
|
||||||
|
|
|
@ -5,7 +5,9 @@ import org.xbib.net.QueryParameters;
|
||||||
import org.xbib.net.path.PathMatcher;
|
import org.xbib.net.path.PathMatcher;
|
||||||
import org.xbib.net.path.PathNormalizer;
|
import org.xbib.net.path.PathNormalizer;
|
||||||
import org.xbib.netty.http.common.HttpMethod;
|
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.Endpoint;
|
||||||
|
import org.xbib.netty.http.server.api.EndpointResolver;
|
||||||
import org.xbib.netty.http.server.api.ServerRequest;
|
import org.xbib.netty.http.server.api.ServerRequest;
|
||||||
import org.xbib.netty.http.server.api.ServerResponse;
|
import org.xbib.netty.http.server.api.ServerResponse;
|
||||||
import org.xbib.netty.http.server.api.Filter;
|
import org.xbib.netty.http.server.api.Filter;
|
||||||
|
@ -82,19 +84,27 @@ public class HttpEndpoint implements Endpoint<HttpEndpointDescriptor> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resolveUriTemplate(ServerRequest serverRequest) throws IOException {
|
public ServerRequest resolveRequest(ServerRequest.Builder serverRequestBuilder,
|
||||||
if (pathMatcher.match(prefix + path, serverRequest.getEffectiveRequestPath())) {
|
Domain<? extends EndpointResolver<? extends Endpoint<?>>> domain,
|
||||||
QueryParameters queryParameters = pathMatcher.extractUriTemplateVariables(prefix + path,
|
EndpointResolver<? extends Endpoint<?>> endpointResolver) {
|
||||||
serverRequest.getEffectiveRequestPath());
|
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) {
|
for (Pair<String, String> pair : queryParameters) {
|
||||||
serverRequest.addPathParameter(pair.getFirst(), pair.getSecond());
|
serverRequestBuilder.addPathParameter(pair.getFirst(), pair.getSecond());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return serverRequestBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void before(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
public void before(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
||||||
serverRequest.setContext(pathMatcher.tokenizePath(getPrefix()));
|
|
||||||
if (serverResponse != null) {
|
if (serverResponse != null) {
|
||||||
for (Filter filter : beforeFilters) {
|
for (Filter filter : beforeFilters) {
|
||||||
filter.handle(serverRequest, serverResponse);
|
filter.handle(serverRequest, serverResponse);
|
||||||
|
@ -104,7 +114,6 @@ public class HttpEndpoint implements Endpoint<HttpEndpointDescriptor> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void after(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
public void after(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
||||||
serverRequest.setContext(pathMatcher.tokenizePath(getPrefix()));
|
|
||||||
if (serverResponse != null) {
|
if (serverResponse != null) {
|
||||||
for (Filter filter : afterFilters) {
|
for (Filter filter : afterFilters) {
|
||||||
filter.handle(serverRequest, serverResponse);
|
filter.handle(serverRequest, serverResponse);
|
||||||
|
|
|
@ -2,9 +2,6 @@ package org.xbib.netty.http.server.endpoint;
|
||||||
|
|
||||||
import org.xbib.netty.http.common.HttpMethod;
|
import org.xbib.netty.http.common.HttpMethod;
|
||||||
import org.xbib.netty.http.server.api.EndpointDescriptor;
|
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> {
|
public class HttpEndpointDescriptor implements EndpointDescriptor, Comparable<HttpEndpointDescriptor> {
|
||||||
|
|
||||||
|
@ -14,10 +11,10 @@ public class HttpEndpointDescriptor implements EndpointDescriptor, Comparable<Ht
|
||||||
|
|
||||||
private final String contentType;
|
private final String contentType;
|
||||||
|
|
||||||
public HttpEndpointDescriptor(ServerRequest serverRequest) {
|
public HttpEndpointDescriptor(String path, HttpMethod method, String contentType) {
|
||||||
this.path = extractPath(serverRequest.getRequestURI());
|
this.path = path;
|
||||||
this.method = Enum.valueOf(HttpMethod.class, serverRequest.getMethod().name());
|
this.method = method;
|
||||||
this.contentType = serverRequest.getHeaders().get(CONTENT_TYPE);
|
this.contentType = contentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -56,13 +53,4 @@ public class HttpEndpointDescriptor implements EndpointDescriptor, Comparable<Ht
|
||||||
public int compareTo(HttpEndpointDescriptor o) {
|
public int compareTo(HttpEndpointDescriptor o) {
|
||||||
return toString().compareTo(o.toString());
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package org.xbib.netty.http.server.endpoint;
|
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.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.EndpointResolver;
|
||||||
|
import org.xbib.netty.http.server.api.Filter;
|
||||||
import org.xbib.netty.http.server.api.ServerRequest;
|
import org.xbib.netty.http.server.api.ServerRequest;
|
||||||
import org.xbib.netty.http.server.api.ServerResponse;
|
import org.xbib.netty.http.server.api.ServerResponse;
|
||||||
import org.xbib.netty.http.server.api.annotation.Endpoint;
|
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 List<HttpEndpoint> endpoints;
|
||||||
|
|
||||||
private final EndpointDispatcher<HttpEndpoint> endpointDispatcher;
|
private final Filter dispatcher;
|
||||||
|
|
||||||
private final Map<HttpEndpointDescriptor, List<HttpEndpoint>> endpointDescriptors;
|
private final Map<HttpEndpointDescriptor, List<HttpEndpoint>> endpointDescriptors;
|
||||||
|
|
||||||
private HttpEndpointResolver(List<HttpEndpoint> endpoints,
|
private HttpEndpointResolver(List<HttpEndpoint> endpoints,
|
||||||
EndpointDispatcher<HttpEndpoint> endpointDispatcher,
|
Filter dispatcher,
|
||||||
int limit) {
|
Integer limit) {
|
||||||
Objects.requireNonNull(endpointDispatcher);
|
|
||||||
this.endpoints = endpoints;
|
this.endpoints = endpoints;
|
||||||
this.endpointDispatcher = endpointDispatcher;
|
this.dispatcher = dispatcher;
|
||||||
this.endpointDescriptors = new LimitedConcurrentHashMap<>(limit);
|
this.endpointDescriptors = new LimitedConcurrentHashMap<>(limit != null ? limit : DEFAULT_LIMIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find matching endpoints for a server request.
|
* Find matching endpoints for a server request.
|
||||||
* @param serverRequest the server request
|
|
||||||
* @return a
|
* @return a
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<HttpEndpoint> matchingEndpointsFor(ServerRequest serverRequest) {
|
public List<HttpEndpoint> matchingEndpointsFor(String path, HttpMethod method, String contentType) {
|
||||||
HttpEndpointDescriptor httpEndpointDescriptor = new HttpEndpointDescriptor(serverRequest);
|
HttpEndpointDescriptor httpEndpointDescriptor = new HttpEndpointDescriptor(path, method, contentType);
|
||||||
endpointDescriptors.putIfAbsent(httpEndpointDescriptor, endpoints.stream()
|
endpointDescriptors.putIfAbsent(httpEndpointDescriptor, endpoints.stream()
|
||||||
.filter(endpoint -> endpoint.matches(httpEndpointDescriptor))
|
.filter(endpoint -> endpoint.matches(httpEndpointDescriptor))
|
||||||
.sorted(new HttpEndpoint.EndpointPathComparator(httpEndpointDescriptor.getSortKey()))
|
.sorted(new HttpEndpoint.EndpointPathComparator(httpEndpointDescriptor.getSortKey()))
|
||||||
|
@ -52,30 +51,12 @@ public class HttpEndpointResolver implements EndpointResolver<HttpEndpoint> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resolve(List<HttpEndpoint> matchingEndpoints,
|
public void handle(HttpEndpoint endpoint,
|
||||||
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,
|
|
||||||
ServerRequest serverRequest,
|
ServerRequest serverRequest,
|
||||||
ServerResponse serverResponse) throws IOException {
|
ServerResponse serverResponse) throws IOException {
|
||||||
Objects.requireNonNull(matchingEndpoints);
|
|
||||||
for (HttpEndpoint endpoint : matchingEndpoints) {
|
|
||||||
endpoint.resolveUriTemplate(serverRequest);
|
|
||||||
endpoint.before(serverRequest, serverResponse);
|
endpoint.before(serverRequest, serverResponse);
|
||||||
endpointDispatcher.dispatch(endpoint, serverRequest, serverResponse);
|
dispatcher.handle(serverRequest, serverResponse);
|
||||||
endpoint.after(serverRequest, serverResponse);
|
endpoint.after(serverRequest, serverResponse);
|
||||||
if (serverResponse != null && serverResponse.getStatus() != null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder builder() {
|
public static Builder builder() {
|
||||||
|
@ -86,11 +67,11 @@ public class HttpEndpointResolver implements EndpointResolver<HttpEndpoint> {
|
||||||
|
|
||||||
private final List<HttpEndpoint> endpoints;
|
private final List<HttpEndpoint> endpoints;
|
||||||
|
|
||||||
private int limit;
|
private Integer limit;
|
||||||
|
|
||||||
private String prefix;
|
private String prefix;
|
||||||
|
|
||||||
private EndpointDispatcher<HttpEndpoint> endpointDispatcher;
|
private Filter dispatcher;
|
||||||
|
|
||||||
Builder() {
|
Builder() {
|
||||||
this.limit = DEFAULT_LIMIT;
|
this.limit = DEFAULT_LIMIT;
|
||||||
|
@ -98,7 +79,7 @@ public class HttpEndpointResolver implements EndpointResolver<HttpEndpoint> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setLimit(int limit) {
|
public Builder setLimit(int limit) {
|
||||||
this.limit = limit > 0 ? limit < 1024 * DEFAULT_LIMIT ? limit : DEFAULT_LIMIT : DEFAULT_LIMIT;
|
this.limit = limit;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,17 +134,20 @@ public class HttpEndpointResolver implements EndpointResolver<HttpEndpoint> {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setDispatcher(EndpointDispatcher<HttpEndpoint> endpointDispatcher) {
|
public Builder setDispatcher(Filter dispatcher) {
|
||||||
Objects.requireNonNull(endpointDispatcher);
|
Objects.requireNonNull(dispatcher);
|
||||||
this.endpointDispatcher = endpointDispatcher;
|
this.dispatcher = dispatcher;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpEndpointResolver build() {
|
public HttpEndpointResolver build() {
|
||||||
|
Objects.requireNonNull(endpoints);
|
||||||
|
Objects.requireNonNull(dispatcher);
|
||||||
|
Objects.requireNonNull(limit);
|
||||||
if (endpoints.isEmpty()) {
|
if (endpoints.isEmpty()) {
|
||||||
throw new IllegalArgumentException("no endpoints configured");
|
throw new IllegalArgumentException("no endpoints configured");
|
||||||
}
|
}
|
||||||
return new HttpEndpointResolver(endpoints, endpointDispatcher, limit);
|
return new HttpEndpointResolver(endpoints, dispatcher, limit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import io.netty.handler.codec.http.HttpHeaders;
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
import io.netty.handler.codec.http.HttpMethod;
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
import io.netty.handler.stream.ChunkedNioStream;
|
import io.netty.handler.stream.ChunkedNioStream;
|
||||||
|
import io.netty.util.AsciiString;
|
||||||
import org.xbib.netty.http.common.util.DateTimeUtil;
|
import org.xbib.netty.http.common.util.DateTimeUtil;
|
||||||
import org.xbib.netty.http.server.api.Filter;
|
import org.xbib.netty.http.server.api.Filter;
|
||||||
import org.xbib.netty.http.server.api.FilterConfig;
|
import org.xbib.netty.http.server.api.FilterConfig;
|
||||||
|
@ -58,24 +59,32 @@ public abstract class ResourceService implements Filter {
|
||||||
|
|
||||||
protected abstract int getMaxAgeSeconds();
|
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)) {
|
if (logger.isLoggable(Level.FINER)) {
|
||||||
logger.log(Level.FINER, "resource = " + resource);
|
logger.log(Level.FINER, "resource = " + resource);
|
||||||
}
|
}
|
||||||
if (resource.isDirectory()) {
|
if (resource.isDirectory()) {
|
||||||
if (!resource.getResourcePath().isEmpty() && !resource.getResourcePath().endsWith("/")) {
|
if (!resource.getResourcePath().isEmpty() && !resource.getResourcePath().endsWith("/")) {
|
||||||
// external redirect to
|
// external redirect to
|
||||||
serverResponse.withHeader(HttpHeaderNames.LOCATION, resource.getResourcePath() + "/");
|
serverResponse.getBuilder()
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.MOVED_PERMANENTLY);
|
.setHeader(HttpHeaderNames.LOCATION, resource.getResourcePath() + "/")
|
||||||
|
.setStatus( HttpResponseStatus.MOVED_PERMANENTLY)
|
||||||
|
.build()
|
||||||
|
.flush();
|
||||||
return;
|
return;
|
||||||
} else if (resource.indexFileName() != null) {
|
} else if (resource.indexFileName() != null) {
|
||||||
// external redirect to default index file in this directory
|
// external redirect to default index file in this directory
|
||||||
serverResponse.withHeader(HttpHeaderNames.LOCATION, resource.indexFileName());
|
serverResponse.getBuilder()
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.MOVED_PERMANENTLY);
|
.setHeader(HttpHeaderNames.LOCATION, resource.indexFileName())
|
||||||
|
.setStatus( HttpResponseStatus.MOVED_PERMANENTLY)
|
||||||
|
.build()
|
||||||
|
.flush();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
// send forbidden, we do not allow directory access
|
// send forbidden, we do not allow directory access
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.FORBIDDEN);
|
serverResponse.getBuilder()
|
||||||
|
.setStatus(HttpResponseStatus.FORBIDDEN)
|
||||||
|
.build().flush();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,8 +97,9 @@ public abstract class ResourceService implements Filter {
|
||||||
String contentType = MimeTypeUtils.guessFromPath(resource.getResourcePath(), false);
|
String contentType = MimeTypeUtils.guessFromPath(resource.getResourcePath(), false);
|
||||||
long expirationMillis = System.currentTimeMillis() + 1000 * getMaxAgeSeconds();
|
long expirationMillis = System.currentTimeMillis() + 1000 * getMaxAgeSeconds();
|
||||||
if (isCacheResponseEnabled()) {
|
if (isCacheResponseEnabled()) {
|
||||||
serverResponse.withHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatRfc1123(expirationMillis))
|
serverResponse.getBuilder()
|
||||||
.withHeader(HttpHeaderNames.CACHE_CONTROL, "public, max-age=" + getMaxAgeSeconds());
|
.setHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatRfc1123(expirationMillis))
|
||||||
|
.setHeader(HttpHeaderNames.CACHE_CONTROL, "public, max-age=" + getMaxAgeSeconds());
|
||||||
}
|
}
|
||||||
boolean sent = false;
|
boolean sent = false;
|
||||||
if (isETagResponseEnabled()) {
|
if (isETagResponseEnabled()) {
|
||||||
|
@ -98,38 +108,48 @@ public abstract class ResourceService implements Filter {
|
||||||
Instant ifUnmodifiedSinceInstant = DateTimeUtil.parseDate(headers.get(HttpHeaderNames.IF_UNMODIFIED_SINCE));
|
Instant ifUnmodifiedSinceInstant = DateTimeUtil.parseDate(headers.get(HttpHeaderNames.IF_UNMODIFIED_SINCE));
|
||||||
if (ifUnmodifiedSinceInstant != null &&
|
if (ifUnmodifiedSinceInstant != null &&
|
||||||
ifUnmodifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) {
|
ifUnmodifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) {
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.PRECONDITION_FAILED);
|
serverResponse.getBuilder()
|
||||||
|
.setStatus(HttpResponseStatus.PRECONDITION_FAILED)
|
||||||
|
.build().flush();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String ifMatch = headers.get(HttpHeaderNames.IF_MATCH);
|
String ifMatch = headers.get(HttpHeaderNames.IF_MATCH);
|
||||||
if (ifMatch != null && !matches(ifMatch, eTag)) {
|
if (ifMatch != null && !matches(ifMatch, eTag)) {
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.PRECONDITION_FAILED);
|
serverResponse.getBuilder()
|
||||||
|
.setStatus(HttpResponseStatus.PRECONDITION_FAILED)
|
||||||
|
.build().flush();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String ifNoneMatch = headers.get(HttpHeaderNames.IF_NONE_MATCH);
|
String ifNoneMatch = headers.get(HttpHeaderNames.IF_NONE_MATCH);
|
||||||
if (ifNoneMatch != null && matches(ifNoneMatch, eTag)) {
|
if (ifNoneMatch != null && matches(ifNoneMatch, eTag)) {
|
||||||
serverResponse.withHeader(HttpHeaderNames.ETAG, eTag)
|
serverResponse.getBuilder()
|
||||||
.withHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatRfc1123(expirationMillis));
|
.setHeader(HttpHeaderNames.ETAG, eTag)
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_MODIFIED);
|
.setHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatRfc1123(expirationMillis))
|
||||||
|
.setStatus(HttpResponseStatus.NOT_MODIFIED)
|
||||||
|
.build().flush();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Instant ifModifiedSinceInstant = DateTimeUtil.parseDate(headers.get(HttpHeaderNames.IF_MODIFIED_SINCE));
|
Instant ifModifiedSinceInstant = DateTimeUtil.parseDate(headers.get(HttpHeaderNames.IF_MODIFIED_SINCE));
|
||||||
if (ifModifiedSinceInstant != null &&
|
if (ifModifiedSinceInstant != null &&
|
||||||
ifModifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) {
|
ifModifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) {
|
||||||
serverResponse.withHeader(HttpHeaderNames.ETAG, eTag)
|
serverResponse.getBuilder()
|
||||||
.withHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatRfc1123(expirationMillis));
|
.setHeader(HttpHeaderNames.ETAG, eTag)
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_MODIFIED);
|
.setHeader(HttpHeaderNames.EXPIRES, DateTimeUtil.formatRfc1123(expirationMillis))
|
||||||
|
.setStatus(HttpResponseStatus.NOT_MODIFIED)
|
||||||
|
.build().flush();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
serverResponse.withHeader(HttpHeaderNames.ETAG, eTag)
|
serverResponse.getBuilder()
|
||||||
.withHeader(HttpHeaderNames.LAST_MODIFIED, DateTimeUtil.formatRfc1123(lastModifiedInstant));
|
.setHeader(HttpHeaderNames.ETAG, eTag)
|
||||||
|
.setHeader(HttpHeaderNames.LAST_MODIFIED, DateTimeUtil.formatRfc1123(lastModifiedInstant));
|
||||||
if (isRangeResponseEnabled()) {
|
if (isRangeResponseEnabled()) {
|
||||||
performRangeResponse(serverRequest, serverResponse, resource, contentType, eTag, headers);
|
performRangeResponse(serverRequest, serverResponse, resource, contentType, eTag, headers);
|
||||||
sent = true;
|
sent = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!sent) {
|
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);
|
send(resource.getURL(), contentType, serverRequest, serverResponse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,16 +157,18 @@ public abstract class ResourceService implements Filter {
|
||||||
private void performRangeResponse(ServerRequest serverRequest, ServerResponse serverResponse,
|
private void performRangeResponse(ServerRequest serverRequest, ServerResponse serverResponse,
|
||||||
Resource resource,
|
Resource resource,
|
||||||
String contentType, String eTag,
|
String contentType, String eTag,
|
||||||
HttpHeaders headers) {
|
HttpHeaders headers) throws IOException {
|
||||||
long length = resource.getLength();
|
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);
|
Range full = new Range(0, length - 1, length);
|
||||||
List<Range> ranges = new ArrayList<>();
|
List<Range> ranges = new ArrayList<>();
|
||||||
String range = headers.get(HttpHeaderNames.RANGE);
|
String range = headers.get(HttpHeaderNames.RANGE);
|
||||||
if (range != null) {
|
if (range != null) {
|
||||||
if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) {
|
if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) {
|
||||||
serverResponse.withHeader(HttpHeaderNames.CONTENT_RANGE, "bytes */" + length);
|
serverResponse.getBuilder()
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
|
.setHeader(HttpHeaderNames.CONTENT_RANGE, "bytes */" + length)
|
||||||
|
.setStatus(HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE)
|
||||||
|
.build().flush();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String ifRange = headers.get(HttpHeaderNames.IF_RANGE);
|
String ifRange = headers.get(HttpHeaderNames.IF_RANGE);
|
||||||
|
@ -171,8 +193,10 @@ public abstract class ResourceService implements Filter {
|
||||||
end = length - 1;
|
end = length - 1;
|
||||||
}
|
}
|
||||||
if (start > end) {
|
if (start > end) {
|
||||||
serverResponse.withHeader(HttpHeaderNames.CONTENT_RANGE, "bytes */" + length);
|
serverResponse.getBuilder()
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
|
.setHeader(HttpHeaderNames.CONTENT_RANGE, "bytes */" + length)
|
||||||
|
.setStatus(HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE)
|
||||||
|
.build().flush();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ranges.add(new Range(start, end, length));
|
ranges.add(new Range(start, end, length));
|
||||||
|
@ -180,16 +204,19 @@ public abstract class ResourceService implements Filter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ranges.isEmpty() || ranges.get(0) == full) {
|
if (ranges.isEmpty() || ranges.get(0) == full) {
|
||||||
serverResponse.withHeader(HttpHeaderNames.CONTENT_RANGE, "bytes " + full.start + '-' + full.end + '/' + full.total)
|
serverResponse.getBuilder()
|
||||||
.withHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(full.length));
|
.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);
|
send(resource.getURL(), HttpResponseStatus.OK, contentType, serverRequest, serverResponse, full.start, full.length);
|
||||||
} else if (ranges.size() == 1) {
|
} else if (ranges.size() == 1) {
|
||||||
Range r = ranges.get(0);
|
Range r = ranges.get(0);
|
||||||
serverResponse.withHeader(HttpHeaderNames.CONTENT_RANGE, "bytes " + r.start + '-' + r.end + '/' + r.total)
|
serverResponse.getBuilder()
|
||||||
.withHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(r.length));
|
.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);
|
send(resource.getURL(), HttpResponseStatus.PARTIAL_CONTENT, contentType, serverRequest, serverResponse, r.start, r.length);
|
||||||
} else {
|
} 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();
|
StringBuilder sb = new StringBuilder();
|
||||||
for (Range r : ranges) {
|
for (Range r : ranges) {
|
||||||
try {
|
try {
|
||||||
|
@ -203,7 +230,11 @@ public abstract class ResourceService implements Filter {
|
||||||
logger.log(Level.FINEST, e.getMessage(), e);
|
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,
|
private void send(URL url, String contentType,
|
||||||
ServerRequest serverRequest, ServerResponse serverResponse) {
|
ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
||||||
if (url == null) {
|
if (url == null) {
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
|
serverResponse.getBuilder()
|
||||||
|
.setStatus( HttpResponseStatus.NOT_FOUND)
|
||||||
|
.build().flush();
|
||||||
} else if (serverRequest.getMethod() == HttpMethod.HEAD) {
|
} else if (serverRequest.getMethod() == HttpMethod.HEAD) {
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.OK, contentType);
|
serverResponse.getBuilder()
|
||||||
|
.setStatus( HttpResponseStatus.OK)
|
||||||
|
.setContentType(contentType)
|
||||||
|
.build().flush();
|
||||||
} else {
|
} else {
|
||||||
if ("file".equals(url.getProtocol())) {
|
if ("file".equals(url.getProtocol())) {
|
||||||
try {
|
try {
|
||||||
send((FileChannel) Files.newByteChannel(Paths.get(url.toURI())),
|
send((FileChannel) Files.newByteChannel(Paths.get(url.toURI())), contentType, serverResponse);
|
||||||
HttpResponseStatus.OK, contentType, serverResponse);
|
|
||||||
} catch (URISyntaxException | IOException e) {
|
} catch (URISyntaxException | IOException e) {
|
||||||
logger.log(Level.SEVERE, e.getMessage(), e);
|
logger.log(Level.SEVERE, e.getMessage(), e);
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
|
serverResponse.getBuilder()
|
||||||
|
.setStatus( HttpResponseStatus.NOT_FOUND)
|
||||||
|
.build().flush();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try (InputStream inputStream = url.openStream()) {
|
try (InputStream inputStream = url.openStream()) {
|
||||||
send(inputStream, HttpResponseStatus.OK, contentType, serverResponse);
|
send(inputStream, contentType, serverResponse);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.log(Level.SEVERE, e.getMessage(), 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,
|
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) {
|
if (url == null) {
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
|
serverResponse.getBuilder()
|
||||||
|
.setStatus( HttpResponseStatus.NOT_FOUND)
|
||||||
|
.build().flush();
|
||||||
} else if (serverRequest.getMethod() == HttpMethod.HEAD) {
|
} else if (serverRequest.getMethod() == HttpMethod.HEAD) {
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.OK, contentType);
|
serverResponse.getBuilder()
|
||||||
|
.setStatus( HttpResponseStatus.OK)
|
||||||
|
.setContentType(contentType)
|
||||||
|
.build().flush();
|
||||||
} else {
|
} else {
|
||||||
if ("file".equals(url.getProtocol())) {
|
if ("file".equals(url.getProtocol())) {
|
||||||
Path path = null;
|
Path path = null;
|
||||||
|
@ -259,52 +303,66 @@ public abstract class ResourceService implements Filter {
|
||||||
contentType, serverResponse, offset, size);
|
contentType, serverResponse, offset, size);
|
||||||
} catch (URISyntaxException | IOException e) {
|
} catch (URISyntaxException | IOException e) {
|
||||||
logger.log(Level.SEVERE, e.getMessage() + " path=" + path, 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 {
|
} else {
|
||||||
try (InputStream inputStream = url.openStream()) {
|
try (InputStream inputStream = url.openStream()) {
|
||||||
send(inputStream, httpResponseStatus, contentType, serverResponse, offset, size);
|
send(inputStream, httpResponseStatus, contentType, serverResponse, offset, size);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.log(Level.SEVERE, e.getMessage(), 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 {
|
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,
|
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) {
|
if (fileChannel == null) {
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
|
serverResponse.getBuilder()
|
||||||
|
.setStatus( HttpResponseStatus.NOT_FOUND)
|
||||||
|
.build().flush();
|
||||||
} else {
|
} else {
|
||||||
MappedByteBuffer mappedByteBuffer = null;
|
MappedByteBuffer mappedByteBuffer = null;
|
||||||
try {
|
try {
|
||||||
mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, offset, size);
|
mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, offset, size);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// resource is not a file that can be mapped
|
// 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) {
|
if (mappedByteBuffer != null) {
|
||||||
serverResponse.withStatus(httpResponseStatus)
|
serverResponse.getBuilder()
|
||||||
.withContentType(contentType)
|
.setStatus(httpResponseStatus)
|
||||||
|
.setContentType(contentType)
|
||||||
|
.build()
|
||||||
.write(Unpooled.wrappedBuffer(mappedByteBuffer));
|
.write(Unpooled.wrappedBuffer(mappedByteBuffer));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType,
|
private void send(InputStream inputStream, String contentType,
|
||||||
ServerResponse serverResponse) throws IOException {
|
ServerResponse serverResponse) throws IOException {
|
||||||
if (inputStream == null) {
|
if (inputStream == null) {
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
|
serverResponse.getBuilder()
|
||||||
|
.setStatus(HttpResponseStatus.NOT_FOUND)
|
||||||
|
.build().flush();
|
||||||
} else {
|
} else {
|
||||||
try (ReadableByteChannel channel = Channels.newChannel(inputStream)) {
|
try (ReadableByteChannel channel = Channels.newChannel(inputStream)) {
|
||||||
serverResponse.withStatus(httpResponseStatus)
|
serverResponse.getBuilder()
|
||||||
.withContentType(contentType)
|
.setStatus(HttpResponseStatus.OK)
|
||||||
|
.setContentType(contentType)
|
||||||
|
.build()
|
||||||
.write(new ChunkedNioStream(channel));
|
.write(new ChunkedNioStream(channel));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -313,10 +371,14 @@ public abstract class ResourceService implements Filter {
|
||||||
private void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType,
|
private void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType,
|
||||||
ServerResponse serverResponse, long offset, long size) throws IOException {
|
ServerResponse serverResponse, long offset, long size) throws IOException {
|
||||||
if (inputStream == null) {
|
if (inputStream == null) {
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
|
serverResponse.getBuilder()
|
||||||
|
.setStatus(HttpResponseStatus.NOT_FOUND)
|
||||||
|
.build().flush();
|
||||||
} else {
|
} else {
|
||||||
serverResponse.withStatus(httpResponseStatus)
|
serverResponse.getBuilder()
|
||||||
.withContentType(contentType)
|
.setStatus(httpResponseStatus)
|
||||||
|
.setContentType(contentType)
|
||||||
|
.build()
|
||||||
.write(Unpooled.wrappedBuffer(readBuffer(inputStream, offset, size)));
|
.write(Unpooled.wrappedBuffer(readBuffer(inputStream, offset, size)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -351,7 +413,7 @@ public abstract class ResourceService implements Filter {
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Range {
|
static class Range {
|
||||||
long start;
|
long start;
|
||||||
long end;
|
long end;
|
||||||
long length;
|
long length;
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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.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> {
|
public class Http1 implements ServerProtocolProvider<Http1ChannelInitializer, Http1Transport> {
|
||||||
|
|
|
@ -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.buffer.Unpooled;
|
||||||
import io.netty.channel.Channel;
|
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.IdleTimeoutHandler;
|
||||||
import org.xbib.netty.http.server.handler.TrafficLoggingHandler;
|
import org.xbib.netty.http.server.handler.TrafficLoggingHandler;
|
||||||
import org.xbib.netty.http.server.api.ServerTransport;
|
import org.xbib.netty.http.server.api.ServerTransport;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
import io.netty.handler.codec.http.LastHttpContent;
|
||||||
|
|
|
@ -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.channel.ChannelPromise;
|
||||||
import io.netty.handler.codec.http.HttpResponse;
|
import io.netty.handler.codec.http.HttpResponse;
|
|
@ -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.ChannelDuplexHandler;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
|
@ -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.ByteBuf;
|
||||||
import io.netty.buffer.ByteBufOutputStream;
|
import io.netty.buffer.ByteBufOutputStream;
|
||||||
|
import io.netty.buffer.ByteBufUtil;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.channel.ChannelFuture;
|
import io.netty.channel.ChannelFuture;
|
||||||
import io.netty.channel.ChannelFutureListener;
|
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.HttpResponseStatus;
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
import io.netty.handler.codec.http.HttpVersion;
|
||||||
import io.netty.handler.stream.ChunkedInput;
|
import io.netty.handler.stream.ChunkedInput;
|
||||||
|
import io.netty.util.AsciiString;
|
||||||
import org.xbib.netty.http.common.cookie.Cookie;
|
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.ServerName;
|
||||||
import org.xbib.netty.http.server.api.ServerRequest;
|
|
||||||
import org.xbib.netty.http.server.api.ServerResponse;
|
import org.xbib.netty.http.server.api.ServerResponse;
|
||||||
import org.xbib.netty.http.server.cookie.ServerCookieEncoder;
|
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.nio.charset.Charset;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
|
@ -38,70 +37,64 @@ public class HttpServerResponse implements ServerResponse {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(HttpServerResponse.class.getName());
|
private static final Logger logger = Logger.getLogger(HttpServerResponse.class.getName());
|
||||||
|
|
||||||
private final Server server;
|
private final Builder builder;
|
||||||
|
|
||||||
private final ServerRequest serverRequest;
|
|
||||||
|
|
||||||
private final ChannelHandlerContext ctx;
|
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) {
|
private final boolean shouldClose;
|
||||||
this.server = server;
|
|
||||||
this.serverRequest = serverRequest;
|
private final boolean shouldAddServerName;
|
||||||
this.ctx = ctx;
|
|
||||||
this.headers = new DefaultHttpHeaders();
|
private final Integer sequenceId;
|
||||||
this.trailingHeaders = new DefaultHttpHeaders();
|
|
||||||
|
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
|
@Override
|
||||||
public ServerResponse withHeader(CharSequence name, String value) {
|
public Builder getBuilder() {
|
||||||
headers.set(name, value);
|
return builder;
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ChannelHandlerContext getChannelHandlerContext() {
|
public Integer getStreamId() {
|
||||||
return ctx;
|
return streamId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpResponseStatus getStatus() {
|
public Integer getSequenceId() {
|
||||||
return httpResponseStatus;
|
return sequenceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ServerResponse withStatus(HttpResponseStatus httpResponseStatus) {
|
public Long getResponseId() {
|
||||||
this.httpResponseStatus = httpResponseStatus;
|
return responseId;
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public static Builder builder(ChannelHandlerContext ctx) {
|
||||||
public ServerResponse withContentType(String contentType) {
|
return new Builder(ctx);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -114,6 +107,16 @@ public class HttpServerResponse implements ServerResponse {
|
||||||
write(Unpooled.buffer(0));
|
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
|
@Override
|
||||||
public void write(byte[] bytes) {
|
public void write(byte[] bytes) {
|
||||||
ByteBuf byteBuf = ctx.alloc().buffer(bytes.length);
|
ByteBuf byteBuf = ctx.alloc().buffer(bytes.length);
|
||||||
|
@ -129,37 +132,30 @@ public class HttpServerResponse implements ServerResponse {
|
||||||
@Override
|
@Override
|
||||||
public void write(ByteBuf byteBuf) {
|
public void write(ByteBuf byteBuf) {
|
||||||
Objects.requireNonNull(byteBuf);
|
Objects.requireNonNull(byteBuf);
|
||||||
if (httpResponseStatus == null) {
|
headers.add(HttpHeaderNames.CONTENT_TYPE, contentType);
|
||||||
httpResponseStatus = HttpResponseStatus.OK;
|
|
||||||
}
|
|
||||||
CharSequence contentType = headers.get(HttpHeaderNames.CONTENT_TYPE);
|
|
||||||
if (contentType == null) {
|
|
||||||
headers.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_OCTET_STREAM);
|
|
||||||
}
|
|
||||||
if (httpResponseStatus.code() >= 200 && httpResponseStatus.code() != 204) {
|
if (httpResponseStatus.code() >= 200 && httpResponseStatus.code() != 204) {
|
||||||
if (!headers.contains(HttpHeaderNames.CONTENT_LENGTH) && !headers.contains(HttpHeaderNames.TRANSFER_ENCODING)) {
|
if (!headers.contains(HttpHeaderNames.CONTENT_LENGTH) && !headers.contains(HttpHeaderNames.TRANSFER_ENCODING)) {
|
||||||
headers.add(HttpHeaderNames.CONTENT_LENGTH, Long.toString(byteBuf.readableBytes()));
|
headers.add(HttpHeaderNames.CONTENT_LENGTH, Long.toString(byteBuf.readableBytes()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (serverRequest != null && "close".equalsIgnoreCase(serverRequest.getHeaders().get(HttpHeaderNames.CONNECTION)) &&
|
if (shouldClose) {
|
||||||
!headers.contains(HttpHeaderNames.CONNECTION)) {
|
|
||||||
headers.add(HttpHeaderNames.CONNECTION, "close");
|
headers.add(HttpHeaderNames.CONNECTION, "close");
|
||||||
}
|
}
|
||||||
if (!headers.contains(HttpHeaderNames.DATE)) {
|
if (!headers.contains(HttpHeaderNames.DATE)) {
|
||||||
headers.add(HttpHeaderNames.DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC)));
|
headers.add(HttpHeaderNames.DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC)));
|
||||||
}
|
}
|
||||||
|
if (shouldAddServerName) {
|
||||||
headers.add(HttpHeaderNames.SERVER, ServerName.getServerName());
|
headers.add(HttpHeaderNames.SERVER, ServerName.getServerName());
|
||||||
|
}
|
||||||
if (ctx.channel().isWritable()) {
|
if (ctx.channel().isWritable()) {
|
||||||
FullHttpResponse fullHttpResponse;
|
FullHttpResponse fullHttpResponse;
|
||||||
fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, httpResponseStatus, byteBuf, headers, trailingHeaders);
|
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,
|
HttpPipelinedResponse httpPipelinedResponse = new HttpPipelinedResponse(fullHttpResponse,
|
||||||
ctx.channel().newPromise(), serverRequest.getSequenceId());
|
ctx.channel().newPromise(), sequenceId);
|
||||||
ctx.channel().writeAndFlush(httpPipelinedResponse);
|
ctx.channel().writeAndFlush(httpPipelinedResponse);
|
||||||
server.getResponseCounter().incrementAndGet();
|
|
||||||
} else {
|
} else {
|
||||||
ctx.channel().writeAndFlush(fullHttpResponse);
|
ctx.channel().writeAndFlush(fullHttpResponse);
|
||||||
server.getResponseCounter().incrementAndGet();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.log(Level.WARNING, "channel not writeable: " + ctx.channel());
|
logger.log(Level.WARNING, "channel not writeable: " + ctx.channel());
|
||||||
|
@ -174,9 +170,6 @@ public class HttpServerResponse implements ServerResponse {
|
||||||
@Override
|
@Override
|
||||||
public void write(ChunkedInput<ByteBuf> chunkedInput) {
|
public void write(ChunkedInput<ByteBuf> chunkedInput) {
|
||||||
Objects.requireNonNull(chunkedInput);
|
Objects.requireNonNull(chunkedInput);
|
||||||
if (httpResponseStatus == null) {
|
|
||||||
httpResponseStatus = HttpResponseStatus.OK;
|
|
||||||
}
|
|
||||||
CharSequence contentType = headers.get(HttpHeaderNames.CONTENT_TYPE);
|
CharSequence contentType = headers.get(HttpHeaderNames.CONTENT_TYPE);
|
||||||
if (contentType == null) {
|
if (contentType == null) {
|
||||||
headers.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_OCTET_STREAM);
|
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);
|
logger.log(Level.FINEST, httpResponse.headers()::toString);
|
||||||
ctx.channel().write(httpResponse);
|
ctx.channel().write(httpResponse);
|
||||||
ChannelFuture channelFuture = ctx.channel().writeAndFlush(new HttpChunkedInput(chunkedInput));
|
ChannelFuture channelFuture = ctx.channel().writeAndFlush(new HttpChunkedInput(chunkedInput));
|
||||||
if ("close".equalsIgnoreCase(serverRequest.getHeaders().get(HttpHeaderNames.CONNECTION)) &&
|
if (shouldClose) {
|
||||||
!headers.contains(HttpHeaderNames.CONNECTION)) {
|
|
||||||
channelFuture.addListener(ChannelFutureListener.CLOSE);
|
channelFuture.addListener(ChannelFutureListener.CLOSE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.log(Level.WARNING, "channel not writeable");
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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.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> {
|
public class Http2 implements ServerProtocolProvider<Http2ChannelInitializer, Http2Transport> {
|
||||||
|
|
|
@ -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.Channel;
|
||||||
import io.netty.channel.ChannelHandler;
|
import io.netty.channel.ChannelHandler;
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +1,2 @@
|
||||||
org.xbib.netty.http.server.Http1
|
org.xbib.netty.http.server.protocol.http1.Http1
|
||||||
org.xbib.netty.http.server.Http2
|
org.xbib.netty.http.server.protocol.http2.Http2
|
||||||
|
|
|
@ -5,7 +5,6 @@ import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.server.Server;
|
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.HttpServerDomain;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -19,7 +18,7 @@ class BindExceptionTest {
|
||||||
@Test
|
@Test
|
||||||
void testDoubleServer() throws IOException {
|
void testDoubleServer() throws IOException {
|
||||||
HttpServerDomain domain = HttpServerDomain.builder(HttpAddress.http1("localhost", 8008))
|
HttpServerDomain domain = HttpServerDomain.builder(HttpAddress.http1("localhost", 8008))
|
||||||
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World"))
|
.singleEndpoint("/", (request, response) -> response.write("Hello World"))
|
||||||
.build();
|
.build();
|
||||||
Server server1 = Server.builder(domain).build();
|
Server server1 = Server.builder(domain).build();
|
||||||
Server server2 = Server.builder(domain).build();
|
Server server2 = Server.builder(domain).build();
|
||||||
|
|
|
@ -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.api.ServerRequest;
|
||||||
import org.xbib.netty.http.server.endpoint.HttpEndpoint;
|
import org.xbib.netty.http.server.endpoint.HttpEndpoint;
|
||||||
import org.xbib.netty.http.server.endpoint.HttpEndpointResolver;
|
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)
|
@ExtendWith(NettyHttpTestExtension.class)
|
||||||
public class ContextURLTest {
|
public class ContextURLTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testServerPublishURL() throws Exception {
|
void testServerPublishURL() {
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
|
|
||||||
HttpEndpointResolver endpointResolver1 = HttpEndpointResolver.builder()
|
HttpEndpointResolver endpointResolver1 = HttpEndpointResolver.builder()
|
||||||
.addEndpoint(HttpEndpoint.builder().setPrefix("/one").setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPrefix("/one").setPath("/**").build())
|
||||||
.setDispatcher((endpoint, serverRequest, serverResponse) -> {})
|
.setDispatcher((serverRequest, serverResponse) -> {})
|
||||||
.build();
|
.build();
|
||||||
HttpEndpointResolver endpointResolver2 = HttpEndpointResolver.builder()
|
HttpEndpointResolver endpointResolver2 = HttpEndpointResolver.builder()
|
||||||
.addEndpoint(HttpEndpoint.builder().setPrefix("/two").setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPrefix("/two").setPath("/**").build())
|
||||||
.setDispatcher((endpoint, serverRequest, serverResponse) -> {})
|
.setDispatcher((serverRequest, serverResponse) -> {})
|
||||||
.build();
|
.build();
|
||||||
HttpEndpointResolver endpointResolver3 = HttpEndpointResolver.builder()
|
HttpEndpointResolver endpointResolver3 = HttpEndpointResolver.builder()
|
||||||
.addEndpoint(HttpEndpoint.builder().setPrefix("/three").setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPrefix("/three").setPath("/**").build())
|
||||||
.setDispatcher((endpoint, serverRequest, serverResponse) -> {})
|
.setDispatcher((serverRequest, serverResponse) -> {})
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
HttpServerDomain one = HttpServerDomain.builder(httpAddress, "domain.one:8008")
|
HttpServerDomain one = HttpServerDomain.builder(httpAddress, "domain.one:8008")
|
||||||
|
@ -48,27 +48,30 @@ public class ContextURLTest {
|
||||||
.addDomain(two)
|
.addDomain(two)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
URL url0 = server.getContextURL();
|
|
||||||
assertEquals("http://localhost:8008/", url0.toString());
|
|
||||||
|
|
||||||
DefaultFullHttpRequest fullHttpRequest1 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/one");
|
DefaultFullHttpRequest fullHttpRequest1 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/one");
|
||||||
fullHttpRequest1.headers().add("host", "domain.one:8008");
|
fullHttpRequest1.headers().add("host", "domain.one:8008");
|
||||||
ServerRequest serverRequest1 = new HttpServerRequest(fullHttpRequest1);
|
ServerRequest serverRequest1 = HttpServerRequest.builder()
|
||||||
URL url1 = server.getContextURL(serverRequest1);
|
.setHttpRequest(fullHttpRequest1)
|
||||||
|
.applyTo(server);
|
||||||
|
String contextPath1 = serverRequest1.getContextPath();
|
||||||
|
assertEquals("/one", contextPath1);
|
||||||
|
URL url1 = serverRequest1.getContextURL();
|
||||||
assertEquals("domain.one", url1.getHost());
|
assertEquals("domain.one", url1.getHost());
|
||||||
assertEquals("/one/", url1.getPath());
|
assertEquals("/one/", url1.getPath());
|
||||||
|
|
||||||
DefaultFullHttpRequest fullHttpRequest2 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/two");
|
DefaultFullHttpRequest fullHttpRequest2 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/two");
|
||||||
fullHttpRequest2.headers().add("host", "domain.two:8008");
|
fullHttpRequest2.headers().add("host", "domain.two:8008");
|
||||||
ServerRequest serverRequest2 = new HttpServerRequest(fullHttpRequest2);
|
ServerRequest serverRequest2 = HttpServerRequest.builder().setHttpRequest(fullHttpRequest2).applyTo(server);
|
||||||
URL url2 = server.getContextURL(serverRequest2);
|
String contextPath2 = serverRequest2.getContextPath();
|
||||||
|
assertEquals("/two", contextPath2);
|
||||||
|
URL url2 = serverRequest2.getContextURL();
|
||||||
assertEquals("domain.two", url2.getHost());
|
assertEquals("domain.two", url2.getHost());
|
||||||
assertEquals("/two/", url2.getPath());
|
assertEquals("/two/", url2.getPath());
|
||||||
|
|
||||||
DefaultFullHttpRequest fullHttpRequest3 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/three");
|
DefaultFullHttpRequest fullHttpRequest3 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/three");
|
||||||
fullHttpRequest3.headers().add("host", "domain.two:8008");
|
fullHttpRequest3.headers().add("host", "domain.two:8008");
|
||||||
ServerRequest serverRequest3 = new HttpServerRequest(fullHttpRequest3);
|
ServerRequest serverRequest3 = HttpServerRequest.builder().setHttpRequest(fullHttpRequest3).applyTo(server);
|
||||||
URL url3 = server.getContextURL(serverRequest3);
|
URL url3 = serverRequest3.getContextURL();
|
||||||
assertEquals("domain.two", url3.getHost());
|
assertEquals("domain.two", url3.getHost());
|
||||||
assertEquals("/three/", url3.getPath());
|
assertEquals("/three/", url3.getPath());
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import org.xbib.netty.http.client.api.Request;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.server.HttpServerDomain;
|
import org.xbib.netty.http.server.HttpServerDomain;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.api.ServerResponse;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
@ -35,11 +34,13 @@ class MultiDomainSecureServerTest {
|
||||||
HttpServerDomain fl = HttpServerDomain.builder(httpAddress, "fl.hbz-nrw.de")
|
HttpServerDomain fl = HttpServerDomain.builder(httpAddress, "fl.hbz-nrw.de")
|
||||||
.setKeyCertChain(certInputStream)
|
.setKeyCertChain(certInputStream)
|
||||||
.setKey(keyInputStream, null)
|
.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();
|
.build();
|
||||||
HttpServerDomain zfl2 = HttpServerDomain.builder(fl)
|
HttpServerDomain zfl2 = HttpServerDomain.builder(fl)
|
||||||
.setServerName("zfl2.hbz-nrw.de")
|
.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();
|
.build();
|
||||||
Server server = Server.builder(fl)
|
Server server = Server.builder(fl)
|
||||||
.addDomain(zfl2)
|
.addDomain(zfl2)
|
||||||
|
|
|
@ -8,7 +8,6 @@ import org.xbib.netty.http.client.Client;
|
||||||
import org.xbib.netty.http.client.api.Request;
|
import org.xbib.netty.http.client.api.Request;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.server.Server;
|
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.HttpServerDomain;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
@ -24,11 +23,13 @@ class MultiDomainServerTest {
|
||||||
void testServer() throws Exception {
|
void testServer() throws Exception {
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
HttpServerDomain fl = HttpServerDomain.builder(httpAddress, "fl.hbz-nrw.de")
|
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();
|
.build();
|
||||||
HttpServerDomain zfl2 = HttpServerDomain.builder(fl)
|
HttpServerDomain zfl2 = HttpServerDomain.builder(fl)
|
||||||
.setServerName("zfl2.hbz-nrw.de")
|
.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();
|
.build();
|
||||||
Server server = Server.builder(fl)
|
Server server = Server.builder(fl)
|
||||||
.addDomain(zfl2)
|
.addDomain(zfl2)
|
||||||
|
|
|
@ -7,7 +7,6 @@ import org.junit.jupiter.api.TestInstance;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.server.Server;
|
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.HttpServerDomain;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -24,7 +23,8 @@ class ThreadLeakTest {
|
||||||
@Test
|
@Test
|
||||||
void testForLeaks() throws IOException {
|
void testForLeaks() throws IOException {
|
||||||
HttpServerDomain domain = HttpServerDomain.builder(HttpAddress.http1("localhost", 8008))
|
HttpServerDomain domain = HttpServerDomain.builder(HttpAddress.http1("localhost", 8008))
|
||||||
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World"))
|
.singleEndpoint("/", (request, response) ->
|
||||||
|
response.write("Hello World"))
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain)
|
Server server = Server.builder(domain)
|
||||||
.setByteBufAllocator(UnpooledByteBufAllocator.DEFAULT)
|
.setByteBufAllocator(UnpooledByteBufAllocator.DEFAULT)
|
||||||
|
|
|
@ -29,9 +29,8 @@ class TransportLayerSecurityServerTest {
|
||||||
Server server = Server.builder(HttpServerDomain.builder(httpAddress)
|
Server server = Server.builder(HttpServerDomain.builder(httpAddress)
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.singleEndpoint("/", (request, response) ->
|
.singleEndpoint("/", (request, response) ->
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
|
||||||
.withContentType("text/plain")
|
.write(request.getContent().toString(StandardCharsets.UTF_8)))
|
||||||
.write(request.getContent().retain()))
|
|
||||||
.build())
|
.build())
|
||||||
.setTransportLayerSecurityProtocols("TLSv1.2")
|
.setTransportLayerSecurityProtocols("TLSv1.2")
|
||||||
.build();
|
.build();
|
||||||
|
@ -67,9 +66,8 @@ class TransportLayerSecurityServerTest {
|
||||||
Server server = Server.builder(HttpServerDomain.builder(httpAddress)
|
Server server = Server.builder(HttpServerDomain.builder(httpAddress)
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.singleEndpoint("/", (request, response) ->
|
.singleEndpoint("/", (request, response) ->
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
|
||||||
.withContentType("text/plain")
|
.write(request.getContent().toString(StandardCharsets.UTF_8)))
|
||||||
.write(request.getContent().retain()))
|
|
||||||
.build())
|
.build())
|
||||||
.setTransportLayerSecurityProtocols("TLSv1.3")
|
.setTransportLayerSecurityProtocols("TLSv1.3")
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -8,13 +8,11 @@ import org.xbib.netty.http.client.Client;
|
||||||
import org.xbib.netty.http.client.api.Request;
|
import org.xbib.netty.http.client.api.Request;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.server.Server;
|
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.HttpEndpoint;
|
||||||
import org.xbib.netty.http.server.endpoint.HttpEndpointResolver;
|
import org.xbib.netty.http.server.endpoint.HttpEndpointResolver;
|
||||||
import org.xbib.netty.http.server.HttpServerDomain;
|
import org.xbib.netty.http.server.HttpServerDomain;
|
||||||
import org.xbib.netty.http.server.endpoint.service.FileService;
|
import org.xbib.netty.http.server.endpoint.service.FileService;
|
||||||
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -24,7 +22,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
@ -40,8 +37,8 @@ class EndpointTest {
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
HttpEndpointResolver httpEndpointResolver = HttpEndpointResolver.builder()
|
HttpEndpointResolver httpEndpointResolver = HttpEndpointResolver.builder()
|
||||||
.addEndpoint(HttpEndpoint.builder().setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPath("/**").build())
|
||||||
.setDispatcher((endpoint, req, resp) -> {
|
.setDispatcher((req, resp) -> {
|
||||||
logger.log(Level.FINE, "dispatching endpoint = " + endpoint +
|
logger.log(Level.FINE, "dispatching endpoint = " + req.getEndpoint() +
|
||||||
" req = " + req + " req context path = " + req.getContextPath());
|
" req = " + req + " req context path = " + req.getContextPath());
|
||||||
fileService.handle(req, resp);
|
fileService.handle(req, resp);
|
||||||
})
|
})
|
||||||
|
@ -81,8 +78,8 @@ class EndpointTest {
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
HttpEndpointResolver httpEndpointResolver = HttpEndpointResolver.builder()
|
HttpEndpointResolver httpEndpointResolver = HttpEndpointResolver.builder()
|
||||||
.addEndpoint(HttpEndpoint.builder().setPrefix("/").setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPrefix("/").setPath("/**").build())
|
||||||
.setDispatcher((endpoint, req, resp) -> {
|
.setDispatcher((req, resp) -> {
|
||||||
logger.log(Level.FINE, "dispatching endpoint = " + endpoint +
|
logger.log(Level.FINE, "dispatching endpoint = " + req.getEndpoint() +
|
||||||
" req = " + req + " req context path = " + req.getContextPath());
|
" req = " + req + " req context path = " + req.getContextPath());
|
||||||
fileService.handle(req, resp);
|
fileService.handle(req, resp);
|
||||||
})
|
})
|
||||||
|
@ -124,8 +121,8 @@ class EndpointTest {
|
||||||
.addEndpoint(HttpEndpoint.builder().setPrefix("/static1").setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPrefix("/static1").setPath("/**").build())
|
||||||
.addEndpoint(HttpEndpoint.builder().setPrefix("/static2").setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPrefix("/static2").setPath("/**").build())
|
||||||
.addEndpoint(HttpEndpoint.builder().setPrefix("/static3").setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPrefix("/static3").setPath("/**").build())
|
||||||
.setDispatcher((endpoint, req, resp) -> {
|
.setDispatcher(( req, resp) -> {
|
||||||
logger.log(Level.FINE, "dispatching endpoint = " + endpoint +
|
logger.log(Level.FINE, "dispatching endpoint = " + req.getEndpoint() +
|
||||||
" req = " + req + " req context path = " + req.getContextPath());
|
" req = " + req + " req context path = " + req.getContextPath());
|
||||||
fileService.handle(req, resp);
|
fileService.handle(req, resp);
|
||||||
})
|
})
|
||||||
|
@ -191,8 +188,8 @@ class EndpointTest {
|
||||||
.addEndpoint(HttpEndpoint.builder().setPrefix("/static1").setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPrefix("/static1").setPath("/**").build())
|
||||||
.addEndpoint(HttpEndpoint.builder().setPrefix("/static2").setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPrefix("/static2").setPath("/**").build())
|
||||||
.addEndpoint(HttpEndpoint.builder().setPrefix("/static3").setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPrefix("/static3").setPath("/**").build())
|
||||||
.setDispatcher((endpoint, req, resp) -> {
|
.setDispatcher((req, resp) -> {
|
||||||
logger.log(Level.FINE, "dispatching endpoint = " + endpoint + " req = " + req);
|
logger.log(Level.FINE, "dispatching endpoint = " + req.getEndpoint() + " req = " + req);
|
||||||
fileService.handle(req, resp);
|
fileService.handle(req, resp);
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
|
@ -274,22 +271,22 @@ class EndpointTest {
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
HttpEndpointResolver httpEndpointResolver1 = HttpEndpointResolver.builder()
|
HttpEndpointResolver httpEndpointResolver1 = HttpEndpointResolver.builder()
|
||||||
.addEndpoint(HttpEndpoint.builder().setPrefix("/static1").setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPrefix("/static1").setPath("/**").build())
|
||||||
.setDispatcher((endpoint, req, resp) -> {
|
.setDispatcher((req, resp) -> {
|
||||||
logger.log(Level.FINE, "dispatching endpoint = " + endpoint + " context path = " + req.getContextPath());
|
logger.log(Level.FINE, "dispatching endpoint = " + req.getEndpoint() + " context path = " + req.getContextPath());
|
||||||
fileService1.handle(req, resp);
|
fileService1.handle(req, resp);
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
HttpEndpointResolver httpEndpointResolver2 = HttpEndpointResolver.builder()
|
HttpEndpointResolver httpEndpointResolver2 = HttpEndpointResolver.builder()
|
||||||
.addEndpoint(HttpEndpoint.builder().setPrefix("/static2").setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPrefix("/static2").setPath("/**").build())
|
||||||
.setDispatcher((endpoint, req, resp) -> {
|
.setDispatcher((req, resp) -> {
|
||||||
logger.log(Level.FINE, "dispatching endpoint = " + endpoint + " context path = " + req.getContextPath());
|
logger.log(Level.FINE, "dispatching endpoint = " + req.getEndpoint() + " context path = " + req.getContextPath());
|
||||||
fileService2.handle(req, resp);
|
fileService2.handle(req, resp);
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
HttpEndpointResolver httpEndpointResolver3 = HttpEndpointResolver.builder()
|
HttpEndpointResolver httpEndpointResolver3 = HttpEndpointResolver.builder()
|
||||||
.addEndpoint(HttpEndpoint.builder().setPrefix("/static3").setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPrefix("/static3").setPath("/**").build())
|
||||||
.setDispatcher((endpoint, req, resp) -> {
|
.setDispatcher((req, resp) -> {
|
||||||
logger.log(Level.FINE, "dispatching endpoint = " + endpoint + " context path = " + req.getContextPath());
|
logger.log(Level.FINE, "dispatching endpoint = " + req.getEndpoint() + " context path = " + req.getContextPath());
|
||||||
fileService3.handle(req, resp);
|
fileService3.handle(req, resp);
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
|
@ -378,7 +375,7 @@ class EndpointTest {
|
||||||
}
|
}
|
||||||
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
||||||
.addEndpointResolver(endpointResolverBuilder
|
.addEndpointResolver(endpointResolverBuilder
|
||||||
.setDispatcher((endpoint,req, resp) -> ServerResponse.write(resp, HttpResponseStatus.OK))
|
.setDispatcher((req, resp) -> resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush())
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain)
|
Server server = Server.builder(domain)
|
||||||
|
@ -419,7 +416,7 @@ class EndpointTest {
|
||||||
for (int i = 0; i < max; i++) {
|
for (int i = 0; i < max; i++) {
|
||||||
domainBuilder.addEndpointResolver(endpointResolverBuilder.addEndpoint(HttpEndpoint.builder()
|
domainBuilder.addEndpointResolver(endpointResolverBuilder.addEndpoint(HttpEndpoint.builder()
|
||||||
.setPath("/" + i + "/**").build())
|
.setPath("/" + i + "/**").build())
|
||||||
.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(domainBuilder.build())
|
Server server = Server.builder(domainBuilder.build())
|
||||||
|
|
|
@ -23,9 +23,9 @@ import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.TestInstance;
|
import org.junit.jupiter.api.TestInstance;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.xbib.netty.http.server.handler.http.HttpPipelinedRequest;
|
import org.xbib.netty.http.server.protocol.http1.HttpPipelinedRequest;
|
||||||
import org.xbib.netty.http.server.handler.http.HttpPipelinedResponse;
|
import org.xbib.netty.http.server.protocol.http1.HttpPipelinedResponse;
|
||||||
import org.xbib.netty.http.server.handler.http.HttpPipeliningHandler;
|
import org.xbib.netty.http.server.protocol.http1.HttpPipeliningHandler;
|
||||||
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
||||||
|
|
||||||
import java.nio.channels.ClosedChannelException;
|
import java.nio.channels.ClosedChannelException;
|
||||||
|
|
|
@ -12,7 +12,6 @@ import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.common.HttpResponse;
|
import org.xbib.netty.http.common.HttpResponse;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.HttpServerDomain;
|
import org.xbib.netty.http.server.HttpServerDomain;
|
||||||
import org.xbib.netty.http.server.api.ServerResponse;
|
|
||||||
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -35,8 +34,8 @@ class CleartextTest {
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
||||||
.singleEndpoint("/**", (request, response) ->
|
.singleEndpoint("/**", (request, response) ->
|
||||||
ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
|
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
|
||||||
request.getContent().toString(StandardCharsets.UTF_8)))
|
.write(request.getContent().toString(StandardCharsets.UTF_8)))
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain).build();
|
Server server = Server.builder(domain).build();
|
||||||
server.accept();
|
server.accept();
|
||||||
|
@ -69,8 +68,8 @@ class CleartextTest {
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
||||||
.singleEndpoint("/**", (request, response) ->
|
.singleEndpoint("/**", (request, response) ->
|
||||||
ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
|
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
|
||||||
request.getContent().toString(StandardCharsets.UTF_8)))
|
.write(request.getContent().toString(StandardCharsets.UTF_8)))
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain).build();
|
Server server = Server.builder(domain).build();
|
||||||
server.accept();
|
server.accept();
|
||||||
|
@ -114,8 +113,8 @@ class CleartextTest {
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
||||||
.singleEndpoint("/**", (request, response) ->
|
.singleEndpoint("/**", (request, response) ->
|
||||||
ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
|
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
|
||||||
request.getContent().toString(StandardCharsets.UTF_8)))
|
.write(request.getContent().toString(StandardCharsets.UTF_8)))
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain).build();
|
Server server = Server.builder(domain).build();
|
||||||
server.accept();
|
server.accept();
|
||||||
|
@ -167,8 +166,6 @@ class CleartextTest {
|
||||||
server.shutdownGracefully(20L, TimeUnit.SECONDS);
|
server.shutdownGracefully(20L, TimeUnit.SECONDS);
|
||||||
client.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() +
|
logger.log(Level.INFO, "client requests = " + client.getRequestCounter() +
|
||||||
" client responses = " + client.getResponseCounter());
|
" client responses = " + client.getResponseCounter());
|
||||||
logger.log(Level.INFO, "expected=" + (threads * loop) + " counter=" + counter.get());
|
logger.log(Level.INFO, "expected=" + (threads * loop) + " counter=" + counter.get());
|
||||||
|
|
|
@ -13,8 +13,8 @@ import org.xbib.netty.http.common.HttpResponse;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.HttpServerDomain;
|
import org.xbib.netty.http.server.HttpServerDomain;
|
||||||
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -35,8 +35,7 @@ class EncryptedTest {
|
||||||
Server server = Server.builder(HttpServerDomain.builder(httpAddress)
|
Server server = Server.builder(HttpServerDomain.builder(httpAddress)
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.singleEndpoint("/", (request, response) ->
|
.singleEndpoint("/", (request, response) ->
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
|
||||||
.withContentType("text/plain")
|
|
||||||
.write(request.getContent().retain()))
|
.write(request.getContent().retain()))
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
@ -66,9 +65,8 @@ class EncryptedTest {
|
||||||
Server server = Server.builder(HttpServerDomain.builder(httpAddress)
|
Server server = Server.builder(HttpServerDomain.builder(httpAddress)
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.singleEndpoint("/", (request, response) ->
|
.singleEndpoint("/", (request, response) ->
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
|
||||||
.withContentType("text/plain")
|
.write(request.getContent().toString(StandardCharsets.UTF_8)))
|
||||||
.write(request.getContent().retain()))
|
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
server.accept();
|
server.accept();
|
||||||
|
@ -110,9 +108,8 @@ class EncryptedTest {
|
||||||
Server server = Server.builder(HttpServerDomain.builder(httpAddress)
|
Server server = Server.builder(HttpServerDomain.builder(httpAddress)
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.singleEndpoint("/", (request, response) ->
|
.singleEndpoint("/", (request, response) ->
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
|
||||||
.withContentType("text/plain")
|
.write(request.getContent().toString(StandardCharsets.UTF_8)))
|
||||||
.write(request.getContent().retain()))
|
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
server.accept();
|
server.accept();
|
||||||
|
|
|
@ -37,7 +37,7 @@ class FlushTest {
|
||||||
.singleEndpoint("/flush", "/**", (req, resp) -> {
|
.singleEndpoint("/flush", "/**", (req, resp) -> {
|
||||||
HttpParameters parameters = req.getParameters();
|
HttpParameters parameters = req.getParameters();
|
||||||
logger.log(Level.INFO, "got request " + parameters.toString() + ", sending 302 Found");
|
logger.log(Level.INFO, "got request " + parameters.toString() + ", sending 302 Found");
|
||||||
resp.withStatus(HttpResponseStatus.FOUND).flush();
|
resp.getBuilder().setStatus(HttpResponseStatus.FOUND).build().flush();
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain)
|
Server server = Server.builder(domain)
|
||||||
|
|
|
@ -17,7 +17,6 @@ import org.xbib.netty.http.common.HttpParameters;
|
||||||
import org.xbib.netty.http.common.HttpResponse;
|
import org.xbib.netty.http.common.HttpResponse;
|
||||||
import org.xbib.netty.http.server.HttpServerDomain;
|
import org.xbib.netty.http.server.HttpServerDomain;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.api.ServerResponse;
|
|
||||||
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
@ -39,7 +38,7 @@ class MimeUploadTest {
|
||||||
logger.log(Level.INFO, "got request, headers = " + req.getHeaders() +
|
logger.log(Level.INFO, "got request, headers = " + req.getHeaders() +
|
||||||
" params = " + parameters.toString() +
|
" params = " + parameters.toString() +
|
||||||
" body = " + req.getContent().toString(StandardCharsets.UTF_8));
|
" body = " + req.getContent().toString(StandardCharsets.UTF_8));
|
||||||
ServerResponse.write(resp, HttpResponseStatus.OK);
|
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
|
||||||
}, "POST")
|
}, "POST")
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain)
|
Server server = Server.builder(domain)
|
||||||
|
|
|
@ -42,7 +42,7 @@ class PostTest {
|
||||||
if ("Jörg".equals(parameters.getFirst("name"))) {
|
if ("Jörg".equals(parameters.getFirst("name"))) {
|
||||||
success3.set(true);
|
success3.set(true);
|
||||||
}
|
}
|
||||||
ServerResponse.write(resp, HttpResponseStatus.OK);
|
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
|
||||||
}, "POST")
|
}, "POST")
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain)
|
Server server = Server.builder(domain)
|
||||||
|
@ -92,7 +92,7 @@ class PostTest {
|
||||||
if ("Jörg".equals(parameters.getFirst("name"))) {
|
if ("Jörg".equals(parameters.getFirst("name"))) {
|
||||||
success3.set(true);
|
success3.set(true);
|
||||||
}
|
}
|
||||||
ServerResponse.write(resp, HttpResponseStatus.OK);
|
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
|
||||||
}, "POST")
|
}, "POST")
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain)
|
Server server = Server.builder(domain)
|
||||||
|
@ -146,7 +146,7 @@ class PostTest {
|
||||||
if ("my value".equals(parameters.getFirst("my param"))) {
|
if ("my value".equals(parameters.getFirst("my param"))) {
|
||||||
success4.set(true);
|
success4.set(true);
|
||||||
}
|
}
|
||||||
ServerResponse.write(resp, HttpResponseStatus.OK);
|
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
|
||||||
}, "POST")
|
}, "POST")
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain)
|
Server server = Server.builder(domain)
|
||||||
|
@ -203,7 +203,7 @@ class PostTest {
|
||||||
if ("my value".equals(parameters.getFirst("my param"))) {
|
if ("my value".equals(parameters.getFirst("my param"))) {
|
||||||
success4.set(true);
|
success4.set(true);
|
||||||
}
|
}
|
||||||
ServerResponse.write(resp, HttpResponseStatus.OK);
|
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
|
||||||
}, "POST")
|
}, "POST")
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain)
|
Server server = Server.builder(domain)
|
||||||
|
@ -257,7 +257,7 @@ class PostTest {
|
||||||
if ("bÿc".equals(parameters.getFirst("a"))) {
|
if ("bÿc".equals(parameters.getFirst("a"))) {
|
||||||
success2.set(true);
|
success2.set(true);
|
||||||
}
|
}
|
||||||
ServerResponse.write(resp, HttpResponseStatus.OK);
|
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
|
||||||
}, "POST")
|
}, "POST")
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain)
|
Server server = Server.builder(domain)
|
||||||
|
|
|
@ -36,7 +36,7 @@ class PutTest {
|
||||||
.singleEndpoint("/put", "/**", (req, resp) -> {
|
.singleEndpoint("/put", "/**", (req, resp) -> {
|
||||||
logger.log(Level.INFO, "got request " +
|
logger.log(Level.INFO, "got request " +
|
||||||
req.getContent().toString(StandardCharsets.UTF_8));
|
req.getContent().toString(StandardCharsets.UTF_8));
|
||||||
ServerResponse.write(resp, HttpResponseStatus.OK);
|
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
|
||||||
success1.set(true);
|
success1.set(true);
|
||||||
}, "PUT")
|
}, "PUT")
|
||||||
.build();
|
.build();
|
||||||
|
@ -84,7 +84,7 @@ class PutTest {
|
||||||
.singleEndpoint("/put", "/**", (req, resp) -> {
|
.singleEndpoint("/put", "/**", (req, resp) -> {
|
||||||
logger.log(Level.INFO, "got request, length = " +
|
logger.log(Level.INFO, "got request, length = " +
|
||||||
req.getContent().readableBytes());
|
req.getContent().readableBytes());
|
||||||
ServerResponse.write(resp, HttpResponseStatus.OK);
|
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
|
||||||
success1.set(true);
|
success1.set(true);
|
||||||
}, "PUT")
|
}, "PUT")
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -31,8 +31,7 @@ class StreamTest {
|
||||||
assertEquals("my body parameter", content);
|
assertEquals("my body parameter", content);
|
||||||
ByteBufOutputStream outputStream = response.getOutputStream();
|
ByteBufOutputStream outputStream = response.getOutputStream();
|
||||||
outputStream.writeBytes("Hello World");
|
outputStream.writeBytes("Hello World");
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
|
||||||
.withContentType("text/plain")
|
|
||||||
.write(outputStream);
|
.write(outputStream);
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -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.HttpAddress;
|
||||||
import org.xbib.netty.http.common.HttpResponse;
|
import org.xbib.netty.http.common.HttpResponse;
|
||||||
import org.xbib.netty.http.server.Server;
|
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.HttpServerDomain;
|
||||||
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
@ -34,8 +32,8 @@ class CleartextTest {
|
||||||
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
||||||
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
||||||
.singleEndpoint("/", (request, response) ->
|
.singleEndpoint("/", (request, response) ->
|
||||||
ServerResponse.write(response, HttpResponseStatus.OK, "text.plain",
|
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
|
||||||
request.getContent().toString(StandardCharsets.UTF_8)))
|
.write(request.getContent().toString(StandardCharsets.UTF_8)))
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain)
|
Server server = Server.builder(domain)
|
||||||
.build();
|
.build();
|
||||||
|
@ -79,9 +77,8 @@ class CleartextTest {
|
||||||
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
||||||
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
||||||
.singleEndpoint("/", (request, response) ->
|
.singleEndpoint("/", (request, response) ->
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
|
||||||
.withContentType("text/plain")
|
.write(request.getContent().toString(StandardCharsets.UTF_8)))
|
||||||
.write(request.getContent().retain()))
|
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain)
|
Server server = Server.builder(domain)
|
||||||
.build();
|
.build();
|
||||||
|
@ -131,8 +128,8 @@ class CleartextTest {
|
||||||
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
||||||
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
||||||
.singleEndpoint("/**", (request, response) ->
|
.singleEndpoint("/**", (request, response) ->
|
||||||
ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
|
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
|
||||||
request.getContent().toString(StandardCharsets.UTF_8)))
|
.write(request.getContent().toString(StandardCharsets.UTF_8)))
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain).build();
|
Server server = Server.builder(domain).build();
|
||||||
server.accept();
|
server.accept();
|
||||||
|
@ -186,8 +183,6 @@ class CleartextTest {
|
||||||
}
|
}
|
||||||
logger.log(Level.INFO, "client requests = " + client.getRequestCounter() +
|
logger.log(Level.INFO, "client requests = " + client.getRequestCounter() +
|
||||||
" client responses = " + client.getResponseCounter());
|
" 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());
|
logger.log(Level.INFO, "expected=" + (threads * loop) + " counter=" + counter.get());
|
||||||
assertEquals(threads * loop , counter.get());
|
assertEquals(threads * loop , counter.get());
|
||||||
}
|
}
|
||||||
|
@ -200,8 +195,8 @@ class CleartextTest {
|
||||||
AtomicInteger counter1 = new AtomicInteger();
|
AtomicInteger counter1 = new AtomicInteger();
|
||||||
HttpServerDomain domain1 = HttpServerDomain.builder(httpAddress1)
|
HttpServerDomain domain1 = HttpServerDomain.builder(httpAddress1)
|
||||||
.singleEndpoint("/", (request, response) -> {
|
.singleEndpoint("/", (request, response) -> {
|
||||||
ServerResponse.write(response, HttpResponseStatus.OK, "text.plain",
|
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
|
||||||
request.getContent().toString(StandardCharsets.UTF_8));
|
.write(request.getContent().toString(StandardCharsets.UTF_8));
|
||||||
counter1.incrementAndGet();
|
counter1.incrementAndGet();
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
|
@ -212,8 +207,8 @@ class CleartextTest {
|
||||||
AtomicInteger counter2 = new AtomicInteger();
|
AtomicInteger counter2 = new AtomicInteger();
|
||||||
HttpServerDomain domain2 = HttpServerDomain.builder(httpAddress2)
|
HttpServerDomain domain2 = HttpServerDomain.builder(httpAddress2)
|
||||||
.singleEndpoint("/", (request, response) -> {
|
.singleEndpoint("/", (request, response) -> {
|
||||||
ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
|
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
|
||||||
request.getContent().toString(StandardCharsets.UTF_8));
|
.write(request.getContent().toString(StandardCharsets.UTF_8));
|
||||||
counter2.incrementAndGet();
|
counter2.incrementAndGet();
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
|
@ -273,10 +268,6 @@ class CleartextTest {
|
||||||
server2.shutdownGracefully();
|
server2.shutdownGracefully();
|
||||||
client.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() +
|
logger.log(Level.INFO, "client requests = " + client.getRequestCounter() +
|
||||||
" client responses = " + client.getResponseCounter());
|
" client responses = " + client.getResponseCounter());
|
||||||
logger.log(Level.INFO, "counter1=" + counter1.get() + " counter2=" + counter2.get());
|
logger.log(Level.INFO, "counter1=" + counter1.get() + " counter2=" + counter2.get());
|
||||||
|
|
|
@ -12,7 +12,6 @@ import org.xbib.netty.http.common.HttpResponse;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.HttpServerDomain;
|
import org.xbib.netty.http.server.HttpServerDomain;
|
||||||
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
@ -34,9 +33,8 @@ class EncryptedTest {
|
||||||
Server server = Server.builder(HttpServerDomain.builder(httpAddress)
|
Server server = Server.builder(HttpServerDomain.builder(httpAddress)
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.singleEndpoint("/", (request, response) ->
|
.singleEndpoint("/", (request, response) ->
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
|
||||||
.withContentType("text/plain")
|
.write(request.getContent().toString(StandardCharsets.UTF_8)))
|
||||||
.write(request.getContent().retain()))
|
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
server.accept();
|
server.accept();
|
||||||
|
@ -75,9 +73,8 @@ class EncryptedTest {
|
||||||
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.singleEndpoint("/", (request, response) ->
|
.singleEndpoint("/", (request, response) ->
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
|
||||||
.withContentType("text/plain")
|
.write(request.getContent().toString(StandardCharsets.UTF_8)))
|
||||||
.write(request.getContent().retain()))
|
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain)
|
Server server = Server.builder(domain)
|
||||||
.build();
|
.build();
|
||||||
|
@ -129,10 +126,8 @@ class EncryptedTest {
|
||||||
Server server = Server.builder(HttpServerDomain.builder(httpAddress)
|
Server server = Server.builder(HttpServerDomain.builder(httpAddress)
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.singleEndpoint("/", (request, response) ->
|
.singleEndpoint("/", (request, response) ->
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.getBuilder().setStatus(HttpResponseStatus.OK).setContentType("text/plain").build()
|
||||||
.withContentType("text/plain")
|
.write(request.getContent().toString(StandardCharsets.UTF_8)))
|
||||||
.write(request.getContent().retain())
|
|
||||||
)
|
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
server.accept();
|
server.accept();
|
||||||
|
|
|
@ -34,7 +34,7 @@ class FlushTest {
|
||||||
.singleEndpoint("/flush", "/**", (req, resp) -> {
|
.singleEndpoint("/flush", "/**", (req, resp) -> {
|
||||||
HttpParameters parameters = req.getParameters();
|
HttpParameters parameters = req.getParameters();
|
||||||
logger.log(Level.INFO, "got request " + parameters.toString() + ", sending 302 Found");
|
logger.log(Level.INFO, "got request " + parameters.toString() + ", sending 302 Found");
|
||||||
resp.withStatus(HttpResponseStatus.FOUND).flush();
|
resp.getBuilder().setStatus(HttpResponseStatus.FOUND).build().flush();
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain)
|
Server server = Server.builder(domain)
|
||||||
|
|
|
@ -10,7 +10,6 @@ import org.xbib.netty.http.client.api.ClientTransport;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.server.HttpServerDomain;
|
import org.xbib.netty.http.server.HttpServerDomain;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.api.ServerResponse;
|
|
||||||
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
|
@ -22,9 +21,7 @@ class MixedProtocolTest {
|
||||||
void testHttp1ClientHttp2Server() throws Exception {
|
void testHttp1ClientHttp2Server() throws Exception {
|
||||||
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
||||||
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
||||||
.singleEndpoint("/", (request, response) -> {
|
.singleEndpoint("/", (request, response) -> response.getBuilder().setStatus(HttpResponseStatus.OK).build().flush())
|
||||||
ServerResponse.write(response, HttpResponseStatus.OK);
|
|
||||||
})
|
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain)
|
Server server = Server.builder(domain)
|
||||||
.build();
|
.build();
|
||||||
|
@ -56,9 +53,8 @@ class MixedProtocolTest {
|
||||||
void testHttp2ClientHttp1Server() throws Exception {
|
void testHttp2ClientHttp1Server() throws Exception {
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
||||||
.singleEndpoint("/", (request, response) -> {
|
.singleEndpoint("/", (request, response) ->
|
||||||
ServerResponse.write(response, HttpResponseStatus.OK);
|
response.getBuilder().setStatus(HttpResponseStatus.OK).build().flush())
|
||||||
})
|
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain)
|
Server server = Server.builder(domain)
|
||||||
.build();
|
.build();
|
||||||
|
@ -94,9 +90,9 @@ class MixedProtocolTest {
|
||||||
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
|
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
|
||||||
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.singleEndpoint("/", (request, response) -> {
|
.singleEndpoint("/", (request, response) ->
|
||||||
ServerResponse.write(response, HttpResponseStatus.OK);
|
response.getBuilder().setStatus(HttpResponseStatus.OK).build().flush()
|
||||||
})
|
)
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain)
|
Server server = Server.builder(domain)
|
||||||
//.enableDebug()
|
//.enableDebug()
|
||||||
|
|
|
@ -11,7 +11,6 @@ import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.common.HttpParameters;
|
import org.xbib.netty.http.common.HttpParameters;
|
||||||
import org.xbib.netty.http.common.HttpResponse;
|
import org.xbib.netty.http.common.HttpResponse;
|
||||||
import org.xbib.netty.http.server.Server;
|
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.HttpServerDomain;
|
||||||
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
||||||
|
|
||||||
|
@ -43,7 +42,7 @@ class PostTest {
|
||||||
if ("Jörg".equals(parameters.getFirst("name"))) {
|
if ("Jörg".equals(parameters.getFirst("name"))) {
|
||||||
success3.set(true);
|
success3.set(true);
|
||||||
}
|
}
|
||||||
ServerResponse.write(resp, HttpResponseStatus.OK);
|
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
|
||||||
}, "POST")
|
}, "POST")
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain)
|
Server server = Server.builder(domain)
|
||||||
|
@ -93,7 +92,7 @@ class PostTest {
|
||||||
if ("Jörg".equals(parameters.getFirst("name"))) {
|
if ("Jörg".equals(parameters.getFirst("name"))) {
|
||||||
success3.set(true);
|
success3.set(true);
|
||||||
}
|
}
|
||||||
ServerResponse.write(resp, HttpResponseStatus.OK);
|
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
|
||||||
}, "POST")
|
}, "POST")
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(domain)
|
Server server = Server.builder(domain)
|
||||||
|
|
|
@ -10,7 +10,6 @@ import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.common.HttpResponse;
|
import org.xbib.netty.http.common.HttpResponse;
|
||||||
import org.xbib.netty.http.server.HttpServerDomain;
|
import org.xbib.netty.http.server.HttpServerDomain;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.api.ServerResponse;
|
|
||||||
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
import org.xbib.netty.http.server.test.NettyHttpTestExtension;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
@ -35,7 +34,7 @@ class PutTest {
|
||||||
.singleEndpoint("/put", "/**", (req, resp) -> {
|
.singleEndpoint("/put", "/**", (req, resp) -> {
|
||||||
logger.log(Level.INFO, "got request " +
|
logger.log(Level.INFO, "got request " +
|
||||||
req.getContent().toString(StandardCharsets.UTF_8));
|
req.getContent().toString(StandardCharsets.UTF_8));
|
||||||
ServerResponse.write(resp, HttpResponseStatus.OK);
|
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
|
||||||
success1.set(true);
|
success1.set(true);
|
||||||
}, "PUT")
|
}, "PUT")
|
||||||
.build();
|
.build();
|
||||||
|
@ -84,7 +83,7 @@ class PutTest {
|
||||||
.singleEndpoint("/put", "/**", (req, resp) -> {
|
.singleEndpoint("/put", "/**", (req, resp) -> {
|
||||||
logger.log(Level.INFO, "got request, length = " +
|
logger.log(Level.INFO, "got request, length = " +
|
||||||
req.getContent().readableBytes());
|
req.getContent().readableBytes());
|
||||||
ServerResponse.write(resp, HttpResponseStatus.OK);
|
resp.getBuilder().setStatus(HttpResponseStatus.OK).build().flush();
|
||||||
success1.set(true);
|
success1.set(true);
|
||||||
}, "PUT")
|
}, "PUT")
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -28,8 +28,9 @@ class StreamTest {
|
||||||
assertEquals("my body parameter", content);
|
assertEquals("my body parameter", content);
|
||||||
ByteBufOutputStream outputStream = response.getOutputStream();
|
ByteBufOutputStream outputStream = response.getOutputStream();
|
||||||
outputStream.writeBytes("Hello World");
|
outputStream.writeBytes("Hello World");
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.getBuilder().setStatus(HttpResponseStatus.OK)
|
||||||
.withContentType("text/plain")
|
.setContentType("text/plain")
|
||||||
|
.build()
|
||||||
.write(outputStream);
|
.write(outputStream);
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
|
|
Loading…
Reference in a new issue