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