diff --git a/build.gradle b/build.gradle index 3005b88..cdf17b4 100644 --- a/build.gradle +++ b/build.gradle @@ -207,6 +207,12 @@ subprojects { // includeFilter = file("config/findbugs/findbugs-include.xml") // excludeFilter = file("config/findbugs/findbugs-excludes.xml") } + + // To generate an HTML report instead of XML + tasks.withType(com.github.spotbugs.SpotBugsTask) { + reports.xml.enabled = false + reports.html.enabled = true + } } sonarqube { diff --git a/gradle.properties b/gradle.properties index 8cec5da..1dc2940 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ group = org.xbib name = netty-http -version = 4.1.36.2 +version = 4.1.36.3 # main packages netty.version = 4.1.36.Final @@ -8,7 +8,7 @@ tcnative.version = 2.0.25.Final alpnagent.version = 2.0.9 # common -xbib-net-url.version = 1.3.1 +xbib-net-url.version = 1.3.2 # server bouncycastle.version = 1.61 diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/Client.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/Client.java index 58ed160..67d3dc8 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/Client.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/Client.java @@ -153,8 +153,8 @@ public final class Client { } } - public static ClientBuilder builder() { - return new ClientBuilder(); + public static Builder builder() { + return new Builder(); } public ClientConfig getClientConfig() { @@ -292,12 +292,8 @@ public final class Client { close(transport); } - public void close(Transport transport) throws IOException { - transport.close(); - transports.remove(transport); - } - - public void close() throws IOException { + public void shutdownGracefully() throws IOException { + logger.log(Level.FINE, "shutting down gracefully"); for (Transport transport : transports) { close(transport); } @@ -305,14 +301,7 @@ public final class Client { if (hasPooledConnections()) { pool.close(); } - } - - public void shutdownGracefully() throws IOException { - close(); - shutdown(); - } - - public void shutdown() { + logger.log(Level.FINE, "shutting down"); eventLoopGroup.shutdownGracefully(); try { eventLoopGroup.awaitTermination(10L, TimeUnit.SECONDS); @@ -321,6 +310,11 @@ public final class Client { } } + private void close(Transport transport) throws IOException { + transport.close(); + transports.remove(transport); + } + /** * Initialize trust manager factory once per client lifecycle. * @param clientConfig the client config @@ -410,7 +404,7 @@ public final class Client { } } - class ClientChannelPoolHandler implements ChannelPoolHandler { + private class ClientChannelPoolHandler implements ChannelPoolHandler { @Override public void channelReleased(Channel channel) { @@ -455,7 +449,7 @@ public final class Client { } } - public static class ClientBuilder { + public static class Builder { private ByteBufAllocator byteBufAllocator; @@ -465,16 +459,16 @@ public final class Client { private ClientConfig clientConfig; - private ClientBuilder() { + private Builder() { this.clientConfig = new ClientConfig(); } - public ClientBuilder enableDebug() { + public Builder enableDebug() { clientConfig.enableDebug(); return this; } - public ClientBuilder disableDebug() { + public Builder disableDebug() { clientConfig.disableDebug(); return this; } @@ -484,187 +478,187 @@ public final class Client { * @param byteBufAllocator the byte buf allocator * @return this builder */ - public ClientBuilder setByteBufAllocator(ByteBufAllocator byteBufAllocator) { + public Builder setByteBufAllocator(ByteBufAllocator byteBufAllocator) { this.byteBufAllocator = byteBufAllocator; return this; } - public ClientBuilder setEventLoop(EventLoopGroup eventLoopGroup) { + public Builder setEventLoop(EventLoopGroup eventLoopGroup) { this.eventLoopGroup = eventLoopGroup; return this; } - public ClientBuilder setChannelClass(Class socketChannelClass) { + public Builder setChannelClass(Class socketChannelClass) { this.socketChannelClass = socketChannelClass; return this; } - public ClientBuilder setThreadCount(int threadCount) { + public Builder setThreadCount(int threadCount) { clientConfig.setThreadCount(threadCount); return this; } - public ClientBuilder setConnectTimeoutMillis(int connectTimeoutMillis) { + public Builder setConnectTimeoutMillis(int connectTimeoutMillis) { clientConfig.setConnectTimeoutMillis(connectTimeoutMillis); return this; } - public ClientBuilder setTcpSendBufferSize(int tcpSendBufferSize) { + public Builder setTcpSendBufferSize(int tcpSendBufferSize) { clientConfig.setTcpSendBufferSize(tcpSendBufferSize); return this; } - public ClientBuilder setTcpReceiveBufferSize(int tcpReceiveBufferSize) { + public Builder setTcpReceiveBufferSize(int tcpReceiveBufferSize) { clientConfig.setTcpReceiveBufferSize(tcpReceiveBufferSize); return this; } - public ClientBuilder setTcpNodelay(boolean tcpNodelay) { + public Builder setTcpNodelay(boolean tcpNodelay) { clientConfig.setTcpNodelay(tcpNodelay); return this; } - public ClientBuilder setKeepAlive(boolean keepAlive) { + public Builder setKeepAlive(boolean keepAlive) { clientConfig.setKeepAlive(keepAlive); return this; } - public ClientBuilder setReuseAddr(boolean reuseAddr) { + public Builder setReuseAddr(boolean reuseAddr) { clientConfig.setReuseAddr(reuseAddr); return this; } - public ClientBuilder setMaxChunkSize(int maxChunkSize) { + public Builder setMaxChunkSize(int maxChunkSize) { clientConfig.setMaxChunkSize(maxChunkSize); return this; } - public ClientBuilder setMaxInitialLineLength(int maxInitialLineLength) { + public Builder setMaxInitialLineLength(int maxInitialLineLength) { clientConfig.setMaxInitialLineLength(maxInitialLineLength); return this; } - public ClientBuilder setMaxHeadersSize(int maxHeadersSize) { + public Builder setMaxHeadersSize(int maxHeadersSize) { clientConfig.setMaxHeadersSize(maxHeadersSize); return this; } - public ClientBuilder setMaxContentLength(int maxContentLength) { + public Builder setMaxContentLength(int maxContentLength) { clientConfig.setMaxContentLength(maxContentLength); return this; } - public ClientBuilder setMaxCompositeBufferComponents(int maxCompositeBufferComponents) { + public Builder setMaxCompositeBufferComponents(int maxCompositeBufferComponents) { clientConfig.setMaxCompositeBufferComponents(maxCompositeBufferComponents); return this; } - public ClientBuilder setReadTimeoutMillis(int readTimeoutMillis) { + public Builder setReadTimeoutMillis(int readTimeoutMillis) { clientConfig.setReadTimeoutMillis(readTimeoutMillis); return this; } - public ClientBuilder setEnableGzip(boolean enableGzip) { + public Builder setEnableGzip(boolean enableGzip) { clientConfig.setEnableGzip(enableGzip); return this; } - public ClientBuilder setSslProvider(SslProvider sslProvider) { + public Builder setSslProvider(SslProvider sslProvider) { clientConfig.setSslProvider(sslProvider); return this; } - public ClientBuilder setJdkSslProvider() { + public Builder setJdkSslProvider() { clientConfig.setJdkSslProvider(); clientConfig.setCiphers(SecurityUtil.Defaults.JDK_CIPHERS); return this; } - public ClientBuilder setOpenSSLSslProvider() { + public Builder setOpenSSLSslProvider() { clientConfig.setOpenSSLSslProvider(); clientConfig.setCiphers(SecurityUtil.Defaults.OPENSSL_CIPHERS); return this; } - public ClientBuilder setSslContextProvider(Provider provider) { + public Builder setSslContextProvider(Provider provider) { clientConfig.setSslContextProvider(provider); return this; } - public ClientBuilder setCiphers(Iterable ciphers) { + public Builder setCiphers(Iterable ciphers) { clientConfig.setCiphers(ciphers); return this; } - public ClientBuilder setCipherSuiteFilter(CipherSuiteFilter cipherSuiteFilter) { + public Builder setCipherSuiteFilter(CipherSuiteFilter cipherSuiteFilter) { clientConfig.setCipherSuiteFilter(cipherSuiteFilter); return this; } - public ClientBuilder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream) { + public Builder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream) { clientConfig.setKeyCert(keyCertChainInputStream, keyInputStream); return this; } - public ClientBuilder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream, - String keyPassword) { + public Builder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream, + String keyPassword) { clientConfig.setKeyCert(keyCertChainInputStream, keyInputStream, keyPassword); return this; } - public ClientBuilder setTrustManagerFactory(TrustManagerFactory trustManagerFactory) { + public Builder setTrustManagerFactory(TrustManagerFactory trustManagerFactory) { clientConfig.setTrustManagerFactory(trustManagerFactory); return this; } - public ClientBuilder trustInsecure() { + public Builder trustInsecure() { clientConfig.setTrustManagerFactory(InsecureTrustManagerFactory.INSTANCE); return this; } - public ClientBuilder setClientAuthMode(ClientAuthMode clientAuthMode) { + public Builder setClientAuthMode(ClientAuthMode clientAuthMode) { clientConfig.setClientAuthMode(clientAuthMode); return this; } - public ClientBuilder setHttpProxyHandler(HttpProxyHandler httpProxyHandler) { + public Builder setHttpProxyHandler(HttpProxyHandler httpProxyHandler) { clientConfig.setHttpProxyHandler(httpProxyHandler); return this; } - public ClientBuilder addPoolNode(HttpAddress httpAddress) { + public Builder addPoolNode(HttpAddress httpAddress) { clientConfig.addPoolNode(httpAddress); clientConfig.setPoolVersion(httpAddress.getVersion()); clientConfig.setPoolSecure(httpAddress.isSecure()); return this; } - public ClientBuilder setPoolNodeConnectionLimit(int nodeConnectionLimit) { + public Builder setPoolNodeConnectionLimit(int nodeConnectionLimit) { clientConfig.setPoolNodeConnectionLimit(nodeConnectionLimit); return this; } - public ClientBuilder setRetriesPerPoolNode(int retriesPerNode) { + public Builder setRetriesPerPoolNode(int retriesPerNode) { clientConfig.setRetriesPerPoolNode(retriesPerNode); return this; } - public ClientBuilder addServerNameForIdentification(String serverName) { + public Builder addServerNameForIdentification(String serverName) { clientConfig.addServerNameForIdentification(serverName); return this; } - public ClientBuilder setHttp2Settings(Http2Settings http2Settings) { + public Builder setHttp2Settings(Http2Settings http2Settings) { clientConfig.setHttp2Settings(http2Settings); return this; } - public ClientBuilder setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark) { + public Builder setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark) { clientConfig.setWriteBufferWaterMark(writeBufferWaterMark); return this; } - public ClientBuilder enableNegotiation(boolean enableNegotiation) { + public Builder enableNegotiation(boolean enableNegotiation) { clientConfig.setEnableNegotiation(enableNegotiation); return this; } diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/Request.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/Request.java index 94b1a6d..f5f3724 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/Request.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/Request.java @@ -2,20 +2,42 @@ package org.xbib.netty.http.client; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.ByteBufUtil; import io.netty.buffer.PooledByteBufAllocator; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpHeaderNames; +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 io.netty.handler.codec.http.HttpVersion; +import io.netty.handler.codec.http.QueryStringDecoder; +import io.netty.handler.codec.http.QueryStringEncoder; import io.netty.handler.codec.http.cookie.Cookie; +import io.netty.handler.codec.http2.HttpConversionUtil; +import io.netty.util.AsciiString; +import org.xbib.net.PercentEncoder; +import org.xbib.net.PercentEncoders; import org.xbib.net.URL; import org.xbib.netty.http.client.listener.CookieListener; import org.xbib.netty.http.client.listener.ResponseListener; import org.xbib.netty.http.client.listener.StatusListener; import org.xbib.netty.http.client.retry.BackOff; +import org.xbib.netty.http.common.HttpAddress; +import org.xbib.netty.http.common.HttpParameters; +import java.nio.charset.MalformedInputException; import java.nio.charset.StandardCharsets; +import java.nio.charset.UnmappableCharacterException; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; /** @@ -57,7 +79,7 @@ public class Request { private StatusListener statusListener; - Request(URL url, String uri, HttpVersion httpVersion, HttpMethod httpMethod, + private Request(URL url, String uri, HttpVersion httpVersion, HttpMethod httpMethod, HttpHeaders headers, Collection cookies, ByteBuf content, long timeoutInMillis, boolean followRedirect, int maxRedirect, int redirectCount, boolean isBackOff, BackOff backOff) { @@ -148,16 +170,14 @@ public class Request { @Override public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("Request[url='").append(url) - .append("',version=").append(httpVersion) - .append(",method=").append(httpMethod) - .append(",headers=").append(headers.entries()) - .append(",content=").append(content != null && content.readableBytes() >= 16 ? - content.copy(0,16).toString(StandardCharsets.UTF_8) + "..." : - content != null ? content.toString(StandardCharsets.UTF_8) : "") - .append("]"); - return sb.toString(); + return "Request[url='" + url + + "',version=" + httpVersion + + ",method=" + httpMethod + + ",headers=" + headers.entries() + + ",content=" + (content != null && content.readableBytes() >= 16 ? + content.copy(0, 16).toString(StandardCharsets.UTF_8) + "..." : + content != null ? content.toString(StandardCharsets.UTF_8) : "") + + "]"; } public Request setCompletableFuture(CompletableFuture completableFuture) { @@ -197,47 +217,398 @@ public class Request { return responseListener; } - public static RequestBuilder get() { + public static Builder get() { return builder(HttpMethod.GET); } - public static RequestBuilder put() { + public static Builder put() { return builder(HttpMethod.PUT); } - public static RequestBuilder post() { + public static Builder post() { return builder(HttpMethod.POST); } - public static RequestBuilder delete() { + public static Builder delete() { return builder(HttpMethod.DELETE); } - public static RequestBuilder head() { + public static Builder head() { return builder(HttpMethod.HEAD); } - public static RequestBuilder patch() { + public static Builder patch() { return builder(HttpMethod.PATCH); } - public static RequestBuilder trace() { + public static Builder trace() { return builder(HttpMethod.TRACE); } - public static RequestBuilder options() { + public static Builder options() { return builder(HttpMethod.OPTIONS); } - public static RequestBuilder connect() { + public static Builder connect() { return builder(HttpMethod.CONNECT); } - public static RequestBuilder builder(HttpMethod httpMethod) { + public static Builder builder(HttpMethod httpMethod) { return builder(PooledByteBufAllocator.DEFAULT, httpMethod); } - public static RequestBuilder builder(ByteBufAllocator allocator, HttpMethod httpMethod) { - return new RequestBuilder(allocator).setMethod(httpMethod); + public static Builder builder(ByteBufAllocator allocator, HttpMethod httpMethod) { + return new Builder(allocator).setMethod(httpMethod); + } + + public static class Builder { + + private static final HttpMethod DEFAULT_METHOD = HttpMethod.GET; + + private static final HttpVersion DEFAULT_HTTP_VERSION = HttpVersion.HTTP_1_1; + + private static final String DEFAULT_USER_AGENT = UserAgent.getUserAgent(); + + private static final URL DEFAULT_URL = URL.from("http://localhost"); + + private static final boolean DEFAULT_GZIP = true; + + private static final boolean DEFAULT_KEEPALIVE = true; + + private static final boolean DEFAULT_FOLLOW_REDIRECT = true; + + private static final long DEFAULT_TIMEOUT_MILLIS = -1L; + + private static final int DEFAULT_MAX_REDIRECT = 10; + + private static final HttpVersion HTTP_2_0 = HttpVersion.valueOf("HTTP/2.0"); + + private static final String DEFAULT_FORM_CONTENT_TYPE = "application/x-www-form-urlencoded; charset=utf-8"; + + private final ByteBufAllocator allocator; + + private final List removeHeaders; + + private final Collection cookies; + + private final PercentEncoder encoder; + + private HttpMethod httpMethod; + + private HttpHeaders headers; + + private HttpVersion httpVersion; + + private String userAgent; + + private boolean keepalive; + + private boolean gzip; + + private URL url; + + private HttpParameters uriParameters; + + private HttpParameters formParameters; + + private ByteBuf content; + + private long timeoutInMillis; + + private boolean followRedirect; + + private int maxRedirects; + + private boolean enableBackOff; + + private BackOff backOff; + + Builder(ByteBufAllocator allocator) { + this.allocator = allocator; + httpMethod = DEFAULT_METHOD; + httpVersion = DEFAULT_HTTP_VERSION; + userAgent = DEFAULT_USER_AGENT; + gzip = DEFAULT_GZIP; + keepalive = DEFAULT_KEEPALIVE; + url = DEFAULT_URL; + timeoutInMillis = DEFAULT_TIMEOUT_MILLIS; + followRedirect = DEFAULT_FOLLOW_REDIRECT; + maxRedirects = DEFAULT_MAX_REDIRECT; + headers = new DefaultHttpHeaders(); + removeHeaders = new ArrayList<>(); + cookies = new HashSet<>(); + encoder = PercentEncoders.getQueryEncoder(StandardCharsets.UTF_8); + uriParameters = new HttpParameters(); + formParameters = new HttpParameters(DEFAULT_FORM_CONTENT_TYPE); + } + + public Builder setMethod(HttpMethod httpMethod) { + this.httpMethod = httpMethod; + return this; + } + + public Builder enableHttp1() { + this.httpVersion = HttpVersion.HTTP_1_1; + return this; + } + + public Builder enableHttp2() { + this.httpVersion = HTTP_2_0; + return this; + } + + public Builder setVersion(HttpVersion httpVersion) { + this.httpVersion = httpVersion; + return this; + } + + public Builder setVersion(String httpVersion) { + this.httpVersion = HttpVersion.valueOf(httpVersion); + return this; + } + + public Builder setTimeoutInMillis(long timeoutInMillis) { + this.timeoutInMillis = timeoutInMillis; + return this; + } + + public Builder remoteAddress(HttpAddress httpAddress) { + this.url = URL.builder() + .scheme(httpAddress.isSecure() ? "https" : "http") + .host(httpAddress.getInetSocketAddress().getHostString()) + .port(httpAddress.getInetSocketAddress().getPort()) + .build(); + this.httpVersion = httpAddress.getVersion(); + return this; + } + + public Builder url(String url) { + return url(URL.from(url)); + } + + public Builder url(URL url) { + this.url = url; + return this; + } + + public Builder uri(String uri) { + this.url = url.resolve(uri); + return this; + } + + public Builder setHeaders(HttpHeaders headers) { + this.headers = headers; + return this; + } + + public Builder addHeader(String name, Object value) { + this.headers.add(name, value); + return this; + } + + public Builder setHeader(String name, Object value) { + this.headers.set(name, value); + return this; + } + + public Builder removeHeader(String name) { + removeHeaders.add(name); + return this; + } + + public Builder addParameter(String name, String value) { + try { + uriParameters.add(encoder.encode(name), encoder.encode(value)); + } catch (MalformedInputException | UnmappableCharacterException e) { + throw new IllegalArgumentException(e); + } + return this; + } + + public Builder addFormParameter(String name, String value) { + try { + formParameters.add(encoder.encode(name), encoder.encode(value)); + } catch (MalformedInputException | UnmappableCharacterException e) { + throw new IllegalArgumentException(e); + } + return this; + } + + public Builder addCookie(Cookie cookie) { + cookies.add(cookie); + return this; + } + + public Builder contentType(String contentType) { + addHeader(HttpHeaderNames.CONTENT_TYPE, contentType); + return this; + } + + public Builder acceptGzip(boolean gzip) { + this.gzip = gzip; + return this; + } + + public Builder keepAlive(boolean keepalive) { + this.keepalive = keepalive; + return this; + } + + public Builder setFollowRedirect(boolean followRedirect) { + this.followRedirect = followRedirect; + return this; + } + + public Builder setMaxRedirects(int maxRedirects) { + this.maxRedirects = maxRedirects; + return this; + } + + public Builder enableBackOff(boolean enableBackOff) { + this.enableBackOff = enableBackOff; + return this; + } + + public Builder setBackOff(BackOff backOff) { + this.backOff = backOff; + return this; + } + + public Builder setUserAgent(String userAgent) { + this.userAgent = userAgent; + return this; + } + + public Builder text(String text) { + content(ByteBufUtil.writeUtf8(allocator, text), HttpHeaderValues.TEXT_PLAIN); + return this; + } + + public Builder json(String json) { + content(ByteBufUtil.writeUtf8(allocator, json), HttpHeaderValues.APPLICATION_JSON); + return this; + } + + public Builder xml(String xml) { + content(xml, "application/xml"); + return this; + } + + public Builder content(ByteBuf byteBuf) { + this.content = byteBuf; + return this; + } + + public Builder content(CharSequence charSequence, String contentType) { + content(charSequence.toString().getBytes(HttpUtil.getCharset(contentType, StandardCharsets.UTF_8)), + AsciiString.of(contentType)); + return this; + } + + public Builder content(byte[] buf, String contentType) { + content(buf, AsciiString.of(contentType)); + return this; + } + + public Builder content(ByteBuf body, String contentType) { + content(body, AsciiString.of(contentType)); + return this; + } + + public Request build() { + if (url == null) { + throw new IllegalStateException("URL not set"); + } + if (url.getHost() == null) { + throw new IllegalStateException("host in URL not defined: " + url); + } + // form parameters + if (!formParameters.isEmpty()) { + try { + // formParameters is already percent encoded + content(formParameters.getAsQueryString(false), formParameters.getContentType()); + } catch (MalformedInputException | UnmappableCharacterException e) { + throw new IllegalArgumentException(); + } + } + // attach user query parameters to URL + URL.Builder builder = url.newBuilder(); + uriParameters.forEach((k, v) -> v.forEach(value -> builder.queryParam(k, value))); + url = builder.build(); + // let Netty's query string decoder/encoder work over the URL to add parameters given implicitly in url() + String path = url.getPath(); + String query = url.getQuery(); + QueryStringDecoder queryStringDecoder = new QueryStringDecoder(query != null ? path + "?" + query : path, StandardCharsets.UTF_8); + QueryStringEncoder queryStringEncoder = new QueryStringEncoder(queryStringDecoder.path()); + for (Map.Entry> entry : queryStringDecoder.parameters().entrySet()) { + for (String value : entry.getValue()) { + queryStringEncoder.addParam(entry.getKey(), value); + } + } + // build uri from QueryStringDecoder + String pathAndQuery = queryStringEncoder.toString(); + StringBuilder sb = new StringBuilder(); + if (!pathAndQuery.isEmpty()) { + sb.append(pathAndQuery); + } + String fragment = url.getFragment(); + if (fragment != null && !fragment.isEmpty()) { + sb.append('#').append(fragment); + } + String uri = sb.toString(); // the encoded form of path/query/fragment + DefaultHttpHeaders validatedHeaders = new DefaultHttpHeaders(true); + validatedHeaders.set(headers); + String scheme = url.getScheme(); + if (httpVersion.majorVersion() == 2) { + validatedHeaders.set(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), scheme); + } + validatedHeaders.set(HttpHeaderNames.HOST, url.getHostInfo()); + validatedHeaders.set(HttpHeaderNames.DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC))); + if (userAgent != null) { + validatedHeaders.set(HttpHeaderNames.USER_AGENT, userAgent); + } + if (gzip) { + validatedHeaders.set(HttpHeaderNames.ACCEPT_ENCODING, "gzip"); + } + int length = content != null ? content.capacity() : 0; + if (!validatedHeaders.contains(HttpHeaderNames.CONTENT_LENGTH) && !validatedHeaders.contains(HttpHeaderNames.TRANSFER_ENCODING)) { + if (length < 0) { + validatedHeaders.set(HttpHeaderNames.TRANSFER_ENCODING, "chunked"); + } else { + validatedHeaders.set(HttpHeaderNames.CONTENT_LENGTH, Long.toString(length)); + } + } + if (!validatedHeaders.contains(HttpHeaderNames.ACCEPT)) { + validatedHeaders.set(HttpHeaderNames.ACCEPT, "*/*"); + } + // RFC 2616 Section 14.10 + // "An HTTP/1.1 client that does not support persistent connections MUST include the "close" connection + // option in every request message." + if (httpVersion.majorVersion() == 1 && !keepalive) { + validatedHeaders.set(HttpHeaderNames.CONNECTION, "close"); + } + // at last, forced removal of unwanted headers + for (String headerName : removeHeaders) { + validatedHeaders.remove(headerName); + } + return new Request(url, uri, httpVersion, httpMethod, validatedHeaders, cookies, content, + timeoutInMillis, followRedirect, maxRedirects, 0, enableBackOff, backOff); + } + + private void addHeader(AsciiString name, Object value) { + if (!headers.contains(name)) { + headers.add(name, value); + } + } + + private void content(byte[] buf, AsciiString contentType) { + content(allocator.buffer().writeBytes(buf), contentType); + } + + private void content(ByteBuf body, AsciiString contentType) { + this.content = body; + addHeader(HttpHeaderNames.CONTENT_LENGTH, (long) body.readableBytes()); + addHeader(HttpHeaderNames.CONTENT_TYPE, contentType); + } } } diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/RequestBuilder.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/RequestBuilder.java deleted file mode 100644 index f561b9d..0000000 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/RequestBuilder.java +++ /dev/null @@ -1,368 +0,0 @@ -package org.xbib.netty.http.client; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.ByteBufUtil; -import io.netty.handler.codec.http.DefaultHttpHeaders; -import io.netty.handler.codec.http.HttpHeaderNames; -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.HttpVersion; -import io.netty.handler.codec.http.QueryStringDecoder; -import io.netty.handler.codec.http.QueryStringEncoder; -import io.netty.handler.codec.http.cookie.Cookie; -import io.netty.handler.codec.http2.HttpConversionUtil; -import io.netty.util.AsciiString; -import org.xbib.net.PercentEncoder; -import org.xbib.net.PercentEncoders; -import org.xbib.net.URL; -import org.xbib.netty.http.client.retry.BackOff; -import org.xbib.netty.http.common.HttpAddress; -import org.xbib.netty.http.common.HttpParameters; - -import java.nio.charset.MalformedInputException; -import java.nio.charset.StandardCharsets; -import java.nio.charset.UnmappableCharacterException; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Map; - -public class RequestBuilder { - - private static final HttpMethod DEFAULT_METHOD = HttpMethod.GET; - - private static final HttpVersion DEFAULT_HTTP_VERSION = HttpVersion.HTTP_1_1; - - private static final String DEFAULT_USER_AGENT = UserAgent.getUserAgent(); - - private static final URL DEFAULT_URL = URL.from("http://localhost"); - - private static final boolean DEFAULT_GZIP = true; - - private static final boolean DEFAULT_KEEPALIVE = true; - - private static final boolean DEFAULT_FOLLOW_REDIRECT = true; - - private static final long DEFAULT_TIMEOUT_MILLIS = -1L; - - private static final int DEFAULT_MAX_REDIRECT = 10; - - private static final HttpVersion HTTP_2_0 = HttpVersion.valueOf("HTTP/2.0"); - - private final ByteBufAllocator allocator; - - private final List removeHeaders; - - private final Collection cookies; - - private final PercentEncoder encoder; - - private HttpMethod httpMethod; - - private HttpHeaders headers; - - private HttpVersion httpVersion; - - private String userAgent; - - private boolean keepalive; - - private boolean gzip; - - private URL url; - - private HttpParameters queryParameters; - - private ByteBuf content; - - private long timeoutInMillis; - - private boolean followRedirect; - - private int maxRedirects; - - private boolean enableBackOff; - - private BackOff backOff; - - RequestBuilder(ByteBufAllocator allocator) { - this.allocator = allocator; - httpMethod = DEFAULT_METHOD; - httpVersion = DEFAULT_HTTP_VERSION; - userAgent = DEFAULT_USER_AGENT; - gzip = DEFAULT_GZIP; - keepalive = DEFAULT_KEEPALIVE; - url = DEFAULT_URL; - timeoutInMillis = DEFAULT_TIMEOUT_MILLIS; - followRedirect = DEFAULT_FOLLOW_REDIRECT; - maxRedirects = DEFAULT_MAX_REDIRECT; - headers = new DefaultHttpHeaders(); - removeHeaders = new ArrayList<>(); - cookies = new HashSet<>(); - encoder = PercentEncoders.getQueryEncoder(StandardCharsets.UTF_8); - queryParameters = new HttpParameters(); - } - - public RequestBuilder setMethod(HttpMethod httpMethod) { - this.httpMethod = httpMethod; - return this; - } - - public RequestBuilder enableHttp1() { - this.httpVersion = HttpVersion.HTTP_1_1; - return this; - } - - public RequestBuilder enableHttp2() { - this.httpVersion = HTTP_2_0; - return this; - } - - public RequestBuilder setVersion(HttpVersion httpVersion) { - this.httpVersion = httpVersion; - return this; - } - - public RequestBuilder setVersion(String httpVersion) { - this.httpVersion = HttpVersion.valueOf(httpVersion); - return this; - } - - public RequestBuilder setTimeoutInMillis(long timeoutInMillis) { - this.timeoutInMillis = timeoutInMillis; - return this; - } - - public RequestBuilder remoteAddress(HttpAddress httpAddress) { - this.url = URL.builder() - .scheme(httpAddress.isSecure() ? "https" : "http") - .host(httpAddress.getInetSocketAddress().getHostString()) - .port(httpAddress.getInetSocketAddress().getPort()) - .build(); - this.httpVersion = httpAddress.getVersion(); - return this; - } - - public RequestBuilder url(String url) { - return url(URL.from(url)); - } - - public RequestBuilder url(URL url) { - this.url = url; - return this; - } - - public RequestBuilder uri(String uri) { - this.url = url.resolve(uri); - return this; - } - - public RequestBuilder setHeaders(HttpHeaders headers) { - this.headers = headers; - return this; - } - - public RequestBuilder addHeader(String name, Object value) { - this.headers.add(name, value); - return this; - } - - public RequestBuilder setHeader(String name, Object value) { - this.headers.set(name, value); - return this; - } - - public RequestBuilder removeHeader(String name) { - removeHeaders.add(name); - return this; - } - - public RequestBuilder addParameter(String name, String value) { - if (queryParameters != null) { - try { - queryParameters.add(name, encoder.encode(value)); - } catch (MalformedInputException | UnmappableCharacterException e) { - throw new IllegalArgumentException(e); - } - } - return this; - } - - public RequestBuilder addCookie(Cookie cookie) { - cookies.add(cookie); - return this; - } - - public RequestBuilder contentType(String contentType) { - addHeader(HttpHeaderNames.CONTENT_TYPE, contentType); - return this; - } - - public RequestBuilder acceptGzip(boolean gzip) { - this.gzip = gzip; - return this; - } - - public RequestBuilder keepAlive(boolean keepalive) { - this.keepalive = keepalive; - return this; - } - - public RequestBuilder setFollowRedirect(boolean followRedirect) { - this.followRedirect = followRedirect; - return this; - } - - public RequestBuilder setMaxRedirects(int maxRedirects) { - this.maxRedirects = maxRedirects; - return this; - } - - public RequestBuilder enableBackOff(boolean enableBackOff) { - this.enableBackOff = enableBackOff; - return this; - } - - public RequestBuilder setBackOff(BackOff backOff) { - this.backOff = backOff; - return this; - } - - public RequestBuilder setUserAgent(String userAgent) { - this.userAgent = userAgent; - return this; - } - - public RequestBuilder text(String text) { - content(text, HttpHeaderValues.TEXT_PLAIN); - return this; - } - - public RequestBuilder json(String json) { - content(json, HttpHeaderValues.APPLICATION_JSON); - return this; - } - - public RequestBuilder xml(String xml) { - content(xml, "application/xml"); - return this; - } - - public RequestBuilder content(ByteBuf byteBuf) { - this.content = byteBuf; - return this; - } - - public RequestBuilder content(CharSequence charSequence, String contentType) { - content(charSequence.toString().getBytes(StandardCharsets.UTF_8), AsciiString.of(contentType)); - return this; - } - - public RequestBuilder content(byte[] buf, String contentType) { - content(buf, AsciiString.of(contentType)); - return this; - } - - public RequestBuilder content(ByteBuf body, String contentType) { - content(body, AsciiString.of(contentType)); - return this; - } - - public Request build() { - if (url == null) { - throw new IllegalStateException("URL not set"); - } - if (url.getHost() == null) { - throw new IllegalStateException("host in URL not defined: " + url); - } - // attach user query parameters to URL - URL.Builder builder = url.newBuilder(); - queryParameters.forEach((k, v) -> v.forEach(value -> builder.queryParam(k, value))); - url = builder.build(); - // let Netty's query string decoder/encoder work over the URL to add parameters given implicitly in url() - String path = url.getPath(); - String query = url.getQuery(); - QueryStringDecoder queryStringDecoder = new QueryStringDecoder(query != null ? path + "?" + query : path, StandardCharsets.UTF_8); - QueryStringEncoder queryStringEncoder = new QueryStringEncoder(queryStringDecoder.path()); - for (Map.Entry> entry : queryStringDecoder.parameters().entrySet()) { - for (String value : entry.getValue()) { - queryStringEncoder.addParam(entry.getKey(), value); - } - } - // build uri from QueryStringDecoder - String pathAndQuery = queryStringEncoder.toString(); - StringBuilder sb = new StringBuilder(); - if (!pathAndQuery.isEmpty()) { - sb.append(pathAndQuery); - } - String fragment = url.getFragment(); - if (fragment != null && !fragment.isEmpty()) { - sb.append('#').append(fragment); - } - String uri = sb.toString(); // the encoded form of path/query/fragment - DefaultHttpHeaders validatedHeaders = new DefaultHttpHeaders(true); - validatedHeaders.set(headers); - String scheme = url.getScheme(); - if (httpVersion.majorVersion() == 2) { - validatedHeaders.set(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), scheme); - } - validatedHeaders.set(HttpHeaderNames.HOST, url.getHostInfo()); - validatedHeaders.set(HttpHeaderNames.DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC))); - if (userAgent != null) { - validatedHeaders.set(HttpHeaderNames.USER_AGENT, userAgent); - } - if (gzip) { - validatedHeaders.set(HttpHeaderNames.ACCEPT_ENCODING, "gzip"); - } - int length = content != null ? content.capacity() : 0; - if (!validatedHeaders.contains(HttpHeaderNames.CONTENT_LENGTH) && !validatedHeaders.contains(HttpHeaderNames.TRANSFER_ENCODING)) { - if (length < 0) { - validatedHeaders.set(HttpHeaderNames.TRANSFER_ENCODING, "chunked"); - } else { - validatedHeaders.set(HttpHeaderNames.CONTENT_LENGTH, Long.toString(length)); - } - } - if (!validatedHeaders.contains(HttpHeaderNames.ACCEPT)) { - validatedHeaders.set(HttpHeaderNames.ACCEPT, "*/*"); - } - // RFC 2616 Section 14.10 - // "An HTTP/1.1 client that does not support persistent connections MUST include the "close" connection - // option in every request message." - if (httpVersion.majorVersion() == 1 && !keepalive) { - validatedHeaders.set(HttpHeaderNames.CONNECTION, "close"); - } - // at last, forced removal of unwanted headers - for (String headerName : removeHeaders) { - validatedHeaders.remove(headerName); - } - return new Request(url, uri, httpVersion, httpMethod, validatedHeaders, cookies, content, - timeoutInMillis, followRedirect, maxRedirects, 0, enableBackOff, backOff); - } - - private void addHeader(AsciiString name, Object value) { - if (!headers.contains(name)) { - headers.add(name, value); - } - } - - private void content(CharSequence charSequence, AsciiString contentType) { - content(ByteBufUtil.writeUtf8(allocator, charSequence), contentType); - } - - private void content(byte[] buf, AsciiString contentType) { - ByteBuf byteBuf = allocator.buffer(); - content(byteBuf.writeBytes(buf), contentType); - } - - private void content(ByteBuf body, AsciiString contentType) { - this.content = body; - addHeader(HttpHeaderNames.CONTENT_LENGTH, (long) body.readableBytes()); - addHeader(HttpHeaderNames.CONTENT_TYPE, contentType); - } -} diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http/HttpChannelInitializer.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http/HttpChannelInitializer.java index 27b0cd6..7ef4248 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http/HttpChannelInitializer.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/handler/http/HttpChannelInitializer.java @@ -10,7 +10,6 @@ import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.logging.LogLevel; import io.netty.handler.ssl.ApplicationProtocolNames; import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler; -import io.netty.handler.ssl.SslHandler; import org.xbib.netty.http.client.Client; import org.xbib.netty.http.client.ClientConfig; import org.xbib.netty.http.client.handler.http2.Http2ChannelInitializer; diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/rest/RestClient.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/rest/RestClient.java index e8579f8..e64aea6 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/rest/RestClient.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/rest/RestClient.java @@ -7,7 +7,6 @@ import org.xbib.net.URL; import org.xbib.netty.http.client.Client; import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.client.Request; -import org.xbib.netty.http.client.RequestBuilder; import java.io.IOException; import java.nio.charset.Charset; @@ -89,7 +88,7 @@ public class RestClient { HttpMethod httpMethod) throws IOException { URL url = URL.create(urlString); RestClient restClient = new RestClient(); - RequestBuilder requestBuilder = Request.builder(httpMethod).url(url); + Request.Builder requestBuilder = Request.builder(httpMethod).url(url); if (byteBuf != null) { requestBuilder.content(byteBuf); } diff --git a/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/BaseTransport.java b/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/BaseTransport.java index cb38236..f5ba92f 100644 --- a/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/BaseTransport.java +++ b/netty-http-client/src/main/java/org/xbib/netty/http/client/transport/BaseTransport.java @@ -11,7 +11,6 @@ import org.xbib.net.URLSyntaxException; import org.xbib.netty.http.client.Client; import org.xbib.netty.http.common.HttpAddress; import org.xbib.netty.http.client.Request; -import org.xbib.netty.http.client.RequestBuilder; import org.xbib.netty.http.client.retry.BackOff; import java.io.IOException; @@ -243,7 +242,7 @@ abstract class BaseTransport implements Transport { logger.log(Level.FINE, "found redirect location: " + location); URL redirUrl = URL.base(request.url()).resolve(location); HttpMethod method = httpResponse.status().code() == 303 ? HttpMethod.GET : request.httpMethod(); - RequestBuilder newHttpRequestBuilder = Request.builder(method) + Request.Builder newHttpRequestBuilder = Request.builder(method) .url(redirUrl) .setVersion(request.httpVersion()) .setHeaders(request.headers()) diff --git a/netty-http-client/src/test/java/org/xbib/netty/http/client/test/Http1Test.java b/netty-http-client/src/test/java/org/xbib/netty/http/client/test/Http1Test.java index 57d42e2..039acab 100644 --- a/netty-http-client/src/test/java/org/xbib/netty/http/client/test/Http1Test.java +++ b/netty-http-client/src/test/java/org/xbib/netty/http/client/test/Http1Test.java @@ -28,7 +28,7 @@ class Http1Test { " status=" + msg.status().code())); client.execute(request).get(); } finally { - client.shutdown(); + client.shutdownGracefully(); } } @@ -47,7 +47,7 @@ class Http1Test { msg.content().toString(StandardCharsets.UTF_8))); client.execute(request2).get(); } finally { - client.shutdown(); + client.shutdownGracefully(); } } diff --git a/netty-http-client/src/test/java/org/xbib/netty/http/client/test/RequestBuilderTest.java b/netty-http-client/src/test/java/org/xbib/netty/http/client/test/RequestBuilderTest.java index 5d65213..7d8c389 100644 --- a/netty-http-client/src/test/java/org/xbib/netty/http/client/test/RequestBuilderTest.java +++ b/netty-http-client/src/test/java/org/xbib/netty/http/client/test/RequestBuilderTest.java @@ -4,7 +4,6 @@ import io.netty.handler.codec.http.HttpMethod; import org.junit.jupiter.api.Test; import org.xbib.net.URL; import org.xbib.netty.http.client.Request; -import org.xbib.netty.http.client.RequestBuilder; import java.net.URI; import java.nio.charset.StandardCharsets; @@ -35,7 +34,7 @@ class RequestBuilderTest { @Test void testRelativeUri() { - RequestBuilder httpRequestBuilder = Request.get(); + Request.Builder httpRequestBuilder = Request.get(); httpRequestBuilder.url("https://localhost").uri("/path"); assertEquals("/path", httpRequestBuilder.build().relative()); httpRequestBuilder.uri("/foobar"); @@ -68,17 +67,17 @@ class RequestBuilderTest { } @Test - void testPostRequest() { + void testBasicPostRequest() { Request request = Request.builder(HttpMethod.POST) .url("http://xbib.org") .addParameter("param1", "value1") .addParameter("param2", "value2") - .content("Hello", "text/plain") + .content("a=b&c=d", "application/x-www-form-urldencoded") .build(); assertEquals("xbib.org", request.url().getHost()); assertEquals("?param1=value1¶m2=value2", request.relative()); assertEquals("http://xbib.org?param1=value1¶m2=value2", request.url().toExternalForm()); - assertEquals("Hello", request.content().toString(StandardCharsets.UTF_8)); + assertEquals("a=b&c=d", request.content().toString(StandardCharsets.UTF_8)); } @Test @@ -112,7 +111,7 @@ class RequestBuilderTest { @Test void testMassiveQueryParameters() { - RequestBuilder requestBuilder = Request.builder(HttpMethod.GET); + Request.Builder requestBuilder = Request.builder(HttpMethod.GET); for (int i = 0; i < 2000; i++) { requestBuilder.addParameter("param" + i, "value" + i); } diff --git a/netty-http-client/src/test/java/org/xbib/netty/http/client/test/SecureHttpTest.java b/netty-http-client/src/test/java/org/xbib/netty/http/client/test/SecureHttpTest.java index 3e1a09e..582f6c9 100644 --- a/netty-http-client/src/test/java/org/xbib/netty/http/client/test/SecureHttpTest.java +++ b/netty-http-client/src/test/java/org/xbib/netty/http/client/test/SecureHttpTest.java @@ -28,7 +28,7 @@ class SecureHttpTest { " status=" + msg.status().code())); client.execute(request).get(); } finally { - client.shutdown(); + client.shutdownGracefully(); } } @@ -47,7 +47,7 @@ class SecureHttpTest { msg.content().toString(StandardCharsets.UTF_8))); client.execute(request2).get(); } finally { - client.shutdown(); + client.shutdownGracefully(); } } diff --git a/netty-http-client/src/test/java/org/xbib/netty/http/client/test/webtide/WebtideTest.java b/netty-http-client/src/test/java/org/xbib/netty/http/client/test/webtide/WebtideTest.java index 17a743a..e22c842 100644 --- a/netty-http-client/src/test/java/org/xbib/netty/http/client/test/webtide/WebtideTest.java +++ b/netty-http-client/src/test/java/org/xbib/netty/http/client/test/webtide/WebtideTest.java @@ -25,7 +25,7 @@ class WebtideTest { .setResponseListener(msg -> logger.log(Level.INFO, "got response: " + msg)); client.execute(request).get(); } finally { - client.shutdown(); + client.shutdownGracefully(); } } diff --git a/netty-http-common/src/main/java/org/xbib/netty/http/common/HttpParameters.java b/netty-http-common/src/main/java/org/xbib/netty/http/common/HttpParameters.java index 75516de..913b5e9 100644 --- a/netty-http-common/src/main/java/org/xbib/netty/http/common/HttpParameters.java +++ b/netty-http-common/src/main/java/org/xbib/netty/http/common/HttpParameters.java @@ -9,8 +9,10 @@ import org.xbib.netty.http.common.util.LimitedStringMap; import java.nio.charset.MalformedInputException; import java.nio.charset.StandardCharsets; import java.nio.charset.UnmappableCharacterException; +import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -28,6 +30,12 @@ import java.util.SortedSet; */ public class HttpParameters implements Map> { + private static final String EQUALS = "="; + + private static final String AMPERSAND = "&"; + + private static final String APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded"; + private final int maxParam; private final int sizeLimit; @@ -40,17 +48,24 @@ public class HttpParameters implements Map> { private final PercentDecoder percentDecoder; + private final String contentType; + public HttpParameters() { - this(1024, 1024, 65536); + this(1024, 1024, 65536, APPLICATION_X_WWW_FORM_URLENCODED); } - public HttpParameters(int maxParam, int sizeLimit, int elementSizeLimit) { + public HttpParameters(String contentType) { + this(1024, 1024, 65536, contentType); + } + + public HttpParameters(int maxParam, int sizeLimit, int elementSizeLimit, String contentType) { this.maxParam = maxParam; this.sizeLimit = sizeLimit; this.elementSizeLimit = elementSizeLimit; this.map = new LimitedStringMap(maxParam); this.percentEncoder = PercentEncoders.getQueryEncoder(StandardCharsets.UTF_8); this.percentDecoder = new PercentDecoder(); + this.contentType = contentType; } @Override @@ -259,6 +274,18 @@ public class HttpParameters implements Map> { return percentDecoder.decode(value); } + public String getContentType() { + return contentType; + } + + public String getAsQueryString(boolean percentEncode) throws MalformedInputException, UnmappableCharacterException { + List list = new ArrayList<>(); + for (String key : keySet()) { + list.add(getAsQueryString(key, percentEncode)); + } + return String.join(AMPERSAND, list); + } + /** * Concatenates all values for the given key to a list of key/value pairs * suitable for use in a URL query string. @@ -289,14 +316,16 @@ public class HttpParameters implements Map> { String k = percentEncode ? percentEncoder.encode(key) : key; SortedSet values = map.get(k); if (values == null) { - return k + "="; + return k + EQUALS; } Iterator it = values.iterator(); StringBuilder sb = new StringBuilder(); while (it.hasNext()) { - sb.append(k).append("=").append(it.next()); + String v = it.next(); + v = percentEncode ? percentEncoder.encode(v) : v; + sb.append(k).append(EQUALS).append(v); if (it.hasNext()) { - sb.append("&"); + sb.append(AMPERSAND); } } return sb.toString(); @@ -311,9 +340,15 @@ public class HttpParameters implements Map> { } public HttpParameters getOAuthParameters() { - HttpParameters oauthParams = new HttpParameters(maxParam, sizeLimit, elementSizeLimit); + HttpParameters oauthParams = new HttpParameters(maxParam, sizeLimit, elementSizeLimit, contentType); entrySet().stream().filter(entry -> entry.getKey().startsWith("oauth_") || entry.getKey().startsWith("x_oauth_")) .forEach(entry -> oauthParams.put(entry.getKey(), entry.getValue())); return oauthParams; } + + @Override + public String toString() { + return new LinkedHashMap<>(this).toString(); + } + } diff --git a/netty-http-common/src/test/java/org/xbib/netty/http/common/HttpParametersTest.java b/netty-http-common/src/test/java/org/xbib/netty/http/common/HttpParametersTest.java new file mode 100644 index 0000000..10e0723 --- /dev/null +++ b/netty-http-common/src/test/java/org/xbib/netty/http/common/HttpParametersTest.java @@ -0,0 +1,27 @@ +package org.xbib.netty.http.common; + +import org.junit.jupiter.api.Test; + +import java.nio.charset.MalformedInputException; +import java.nio.charset.UnmappableCharacterException; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class HttpParametersTest { + + @Test + void testParameters() throws MalformedInputException, UnmappableCharacterException { + HttpParameters httpParameters = new HttpParameters(); + httpParameters.add("a", "b"); + String query = httpParameters.getAsQueryString(false); + assertEquals("a=b", query); + } + + @Test + void testUtf8() throws MalformedInputException, UnmappableCharacterException { + HttpParameters httpParameters = new HttpParameters("text/plain; charset=utf-8"); + httpParameters.add("Hello", "Jörg"); + String query = httpParameters.getAsQueryString(false); + assertEquals("Hello=Jörg", query); + } +} diff --git a/netty-http-server-rest/src/main/java/org/xbib/netty/http/server/rest/util/MediaType.java b/netty-http-server-rest/src/main/java/org/xbib/netty/http/server/rest/util/MediaType.java index 80d3720..2e0d9b7 100644 --- a/netty-http-server-rest/src/main/java/org/xbib/netty/http/server/rest/util/MediaType.java +++ b/netty-http-server-rest/src/main/java/org/xbib/netty/http/server/rest/util/MediaType.java @@ -1,5 +1,6 @@ package org.xbib.netty.http.server.rest.util; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; @@ -1153,13 +1154,13 @@ public class MediaType { private final String contentType; private MediaType(String contentType) { - this.bytes = contentType.getBytes(); + this.bytes = contentType.getBytes(StandardCharsets.UTF_8); this.contentType = contentType; } private MediaType(String name, String[] attributes) { - this.bytes = join(name, attributes).getBytes(); - this.contentType = new String(this.bytes); + this.bytes = join(name, attributes).getBytes(StandardCharsets.UTF_8); + this.contentType = new String(this.bytes, StandardCharsets.UTF_8); } private static MediaType create(String type, String... fileExtensisons) { diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java index 4cb561f..ed8125e 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/Server.java @@ -193,6 +193,7 @@ public final class Server { } public synchronized void shutdownGracefully() throws IOException { + logger.log(Level.FINE, "shutting down gracefully"); // first, shut down threads, then server socket childEventLoopGroup.shutdownGracefully(); parentEventLoopGroup.shutdownGracefully(); diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/ServerRequest.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/ServerRequest.java index acb7911..b62493e 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/ServerRequest.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/ServerRequest.java @@ -2,8 +2,10 @@ package org.xbib.netty.http.server; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.FullHttpRequest; +import org.xbib.netty.http.common.HttpParameters; import org.xbib.netty.http.server.endpoint.NamedServer; +import java.io.IOException; import java.util.List; import java.util.Map; @@ -19,9 +21,13 @@ public interface ServerRequest { List getContext(); - void setRawParameters(Map rawParameters); + void setPathParameters(Map rawParameters); - Map getRawParameters(); + Map getPathParameters(); + + void createParameters() throws IOException; + + HttpParameters getParameters(); String getContextPath(); diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/Endpoint.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/Endpoint.java index b887c23..eba766e 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/Endpoint.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/Endpoint.java @@ -77,7 +77,7 @@ public class Endpoint { for (QueryParameters.Pair pair : queryParameters) { map.put(pair.getFirst(), pair.getSecond()); } - serverRequest.setRawParameters(map); + serverRequest.setPathParameters(map); } } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/EndpointResolver.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/EndpointResolver.java index 2e3bd6b..97d8642 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/EndpointResolver.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/EndpointResolver.java @@ -12,14 +12,10 @@ import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.stream.Collectors; public class EndpointResolver { - private static final Logger logger = Logger.getLogger(EndpointResolver.class.getName()); - private final Endpoint defaultEndpoint; private final List endpoints; @@ -44,9 +40,6 @@ public class EndpointResolver { .filter(endpoint -> endpoint.matches(endpointInfo)) .sorted(new Endpoint.EndpointPathComparator(serverRequest.getEffectiveRequestPath())).collect(Collectors.toList())); List matchingEndpoints = cache.get(endpointInfo); - if (logger.isLoggable(Level.FINER)) { - logger.log(Level.FINER, "matching endpoints = " + matchingEndpoints); - } if (matchingEndpoints.isEmpty()) { if (defaultEndpoint != null) { defaultEndpoint.resolveUriTemplate(serverRequest); @@ -148,8 +141,9 @@ public class EndpointResolver { } /** + * Add endpoint. * - * @param endpoint + * @param endpoint the endpoint * @return this builder */ public Builder addEndpoint(Endpoint endpoint) { @@ -164,6 +158,8 @@ public class EndpointResolver { /** * Adds a service for the methods of the given object that * are annotated with the {@link Context} annotation. + * @param classWithAnnotatedMethods class with annotated methods + * @return this builder */ public Builder addEndpoint(Object classWithAnnotatedMethods) { for (Class clazz = classWithAnnotatedMethods.getClass(); clazz != null; clazz = clazz.getSuperclass()) { diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/NamedServer.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/NamedServer.java index 7d80791..f9c1dfd 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/NamedServer.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/NamedServer.java @@ -20,6 +20,7 @@ import java.io.InputStream; import java.security.KeyStore; import java.security.Provider; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; @@ -258,6 +259,13 @@ public class NamedServer { return this; } + public Builder singleEndpoint(String prefix, String path, Service service, String... methods) { + addEndpointResolver(EndpointResolver.builder() + .addEndpoint(Endpoint.builder().setPrefix(prefix).setPath(path).addFilter(service) + .setMethods(Arrays.asList(methods)).build()).build()); + return this; + } + public NamedServer build() { if (httpAddress.isSecure()) { try { diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/ClasspathService.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/ClasspathService.java index a82de2c..301ec81 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/ClasspathService.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/endpoint/service/ClasspathService.java @@ -8,44 +8,87 @@ import org.xbib.netty.http.server.ServerResponse; import org.xbib.netty.http.server.util.MimeTypeUtils; import java.io.IOException; +import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; +import java.nio.file.FileSystem; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; public class ClasspathService implements Service { - private final ClassLoader classLoader; + private static final Logger logger = Logger.getLogger(ClasspathService.class.getName()); + + private Class clazz; private final String prefix; - public ClasspathService(ClassLoader classLoader, String prefix) { - this.classLoader = classLoader; + private final Map env; + + public ClasspathService(Class clazz, String prefix) { + this.clazz = clazz; this.prefix = prefix; + this.env = new HashMap<>(); + env.put("create", "true"); } @Override - public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException { + public void handle(ServerRequest serverRequest, ServerResponse serverResponse) { String requestPath = serverRequest.getEffectiveRequestPath(); - URL url = classLoader.getResource(prefix + requestPath); + String contentType = MimeTypeUtils.guessFromPath(requestPath, false); + URL url = clazz.getResource(prefix + "/" + requestPath); if (url != null) { try { - FileChannel fileChannel = (FileChannel) Files.newByteChannel(Paths.get(url.toURI())); - MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size()); - ByteBuf byteBuf = Unpooled.wrappedBuffer(mappedByteBuffer); - try { - String contentType = MimeTypeUtils.guessFromPath(requestPath, false); - serverResponse.write(HttpResponseStatus.OK, contentType, byteBuf); - } finally { - byteBuf.release(); + if ("jar".equals(url.getProtocol())) { + doJarResource(url.toURI(), contentType, serverResponse); + } else { + doFileResource(url.toURI(), contentType, serverResponse); } - } catch (URISyntaxException e) { + } catch (IOException | URISyntaxException e) { + logger.log(Level.SEVERE, e.getMessage(), e); serverResponse.write(HttpResponseStatus.INTERNAL_SERVER_ERROR); } } else { serverResponse.write(HttpResponseStatus.NOT_FOUND); } } + + private void doFileResource(URI uri, String contentType, ServerResponse serverResponse) { + try { + FileChannel fileChannel = (FileChannel) Files.newByteChannel(Paths.get(uri)); + MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size()); + ByteBuf byteBuf = Unpooled.wrappedBuffer(mappedByteBuffer); + serverResponse.write(HttpResponseStatus.OK, contentType, byteBuf); + } catch (IOException e) { + logger.log(Level.SEVERE, e.getMessage(), e); + serverResponse.write(HttpResponseStatus.INTERNAL_SERVER_ERROR); + } + } + + @SuppressWarnings("try") + private void doJarResource(URI uri, String contentType, ServerResponse serverResponse) throws IOException { + FileSystem zipfs = null; + try { + try { + zipfs = FileSystems.getFileSystem(uri); + } catch (FileSystemNotFoundException e) { + zipfs = FileSystems.newFileSystem(uri, env); + } + ByteBuf byteBuf = Unpooled.wrappedBuffer(Files.readAllBytes(Paths.get(uri))); + serverResponse.write(HttpResponseStatus.OK, contentType, byteBuf); + } catch (IOException e) { + logger.log(Level.SEVERE, e.getMessage(), e); + serverResponse.write(HttpResponseStatus.INTERNAL_SERVER_ERROR); + } + } + + } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/BaseServerTransport.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/BaseServerTransport.java index 6d0a2e1..930fd85 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/BaseServerTransport.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/BaseServerTransport.java @@ -79,6 +79,8 @@ abstract class BaseServerTransport implements ServerTransport { * @throws IOException if and error occurs */ static void handle(HttpServerRequest serverRequest, ServerResponse serverResponse) throws IOException { + // parse parameters from path and parse body, if required + serverRequest.createParameters(); serverRequest.getNamedServer().execute(serverRequest, serverResponse); } } diff --git a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/HttpServerRequest.java b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/HttpServerRequest.java index 96eb061..bc9b6ac 100644 --- a/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/HttpServerRequest.java +++ b/netty-http-server/src/main/java/org/xbib/netty/http/server/transport/HttpServerRequest.java @@ -1,30 +1,47 @@ package org.xbib.netty.http.server.transport; +import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpUtil; +import org.xbib.net.QueryParameters; +import org.xbib.net.URL; +import org.xbib.netty.http.common.HttpParameters; import org.xbib.netty.http.server.ServerRequest; import org.xbib.netty.http.server.endpoint.NamedServer; +import java.io.IOException; +import java.nio.charset.MalformedInputException; +import java.nio.charset.StandardCharsets; +import java.nio.charset.UnmappableCharacterException; 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 static final CharSequence APPLICATION_FORM_URL_ENCODED = "application/x-www-form-urlencoded"; + private NamedServer namedServer; private ChannelHandlerContext ctx; private List context; - private Map rawParameters; + private Map pathParameters; private FullHttpRequest httpRequest; + private HttpParameters parameters; + private Integer sequenceId; private Integer streamId; @@ -79,13 +96,27 @@ public class HttpServerRequest implements ServerRequest { uri.substring(getContextPath().length() + 2) : uri; } - public void setRawParameters(Map rawParameters) { - this.rawParameters = rawParameters; + public void setPathParameters(Map pathParameters) { + this.pathParameters = pathParameters; } @Override - public Map getRawParameters() { - return rawParameters; + public Map getPathParameters() { + return pathParameters; + } + + @Override + public void createParameters() throws IOException { + try { + buildParameters(); + } catch (MalformedInputException | UnmappableCharacterException e) { + throw new IOException(e); + } + } + + @Override + public HttpParameters getParameters() { + return parameters; } public void setSequenceId(Integer sequenceId) { @@ -115,6 +146,26 @@ public class HttpServerRequest implements ServerRequest { return requestId; } + private void buildParameters() throws MalformedInputException, UnmappableCharacterException { + HttpParameters httpParameters = new HttpParameters(); + URL.Builder builder = URL.builder().path(getEffectiveRequestPath()); + if (pathParameters != null && !pathParameters.isEmpty()) { + for (Map.Entry entry : pathParameters.entrySet()) { + builder.queryParam(entry.getKey(), entry.getValue()); + } + } + QueryParameters queryParameters = builder.build().getQueryParams(); + ByteBuf byteBuf = httpRequest.content(); + if (APPLICATION_FORM_URL_ENCODED.equals(HttpUtil.getMimeType(httpRequest)) && byteBuf != null) { + String content = byteBuf.toString(HttpUtil.getCharset(httpRequest, StandardCharsets.ISO_8859_1)); + queryParameters.addPercentEncodedBody(content); + } + for (QueryParameters.Pair pair : queryParameters) { + httpParameters.add(pair.getFirst(), pair.getSecond()); + } + this.parameters = httpParameters; + } + public String toString() { return "ServerRequest[namedServer=" + namedServer + ",context=" + context + diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/ClassloaderServiceTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/ClassloaderServiceTest.java new file mode 100644 index 0000000..70f7f5e --- /dev/null +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/ClassloaderServiceTest.java @@ -0,0 +1,60 @@ +package org.xbib.netty.http.server.test; + +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.xbib.netty.http.client.Client; +import org.xbib.netty.http.client.Request; +import org.xbib.netty.http.common.HttpAddress; +import org.xbib.netty.http.server.Server; +import org.xbib.netty.http.server.endpoint.NamedServer; +import org.xbib.netty.http.server.endpoint.service.ClasspathService; + +import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@ExtendWith(NettyHttpExtension.class) +class ClassloaderServiceTest { + + private static final Logger logger = Logger.getLogger(ClassloaderServiceTest.class.getName()); + + @Test + void testClassloader() throws Exception { + HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); + NamedServer namedServer = NamedServer.builder(httpAddress) + .singleEndpoint("/classloader", "/**", new ClasspathService(ClassloaderServiceTest.class, "/cl")) + .build(); + Server server = Server.builder(namedServer) + .build(); + server.logDiagnostics(Level.INFO); + Client client = Client.builder() + .build(); + int max = 100; + final AtomicInteger count = new AtomicInteger(0); + try { + server.accept(); + Request request = Request.get().setVersion(HttpVersion.HTTP_1_1) + .url(server.getServerConfig().getAddress().base().resolve("/classloader/test.txt")) + .build() + .setResponseListener(r -> { + if (r.status().equals(HttpResponseStatus.OK)) { + assertEquals("Hello Jörg", r.content().toString(StandardCharsets.UTF_8)); + count.incrementAndGet(); + } + }); + for (int i = 0; i < max; i++) { + client.execute(request).get(); + } + } finally { + server.shutdownGracefully(); + client.shutdownGracefully(); + logger.log(Level.INFO, "server and client shut down"); + } + assertEquals(max, count.get()); + } +} diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/PostTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/PostTest.java new file mode 100644 index 0000000..8392a98 --- /dev/null +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/PostTest.java @@ -0,0 +1,99 @@ +package org.xbib.netty.http.server.test; + +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpVersion; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.xbib.netty.http.client.Client; +import org.xbib.netty.http.client.Request; +import org.xbib.netty.http.common.HttpAddress; +import org.xbib.netty.http.common.HttpParameters; +import org.xbib.netty.http.server.Server; +import org.xbib.netty.http.server.endpoint.NamedServer; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith(NettyHttpExtension.class) +class PostTest { + + private static final Logger logger = Logger.getLogger(PostTest.class.getName()); + + @Test + void testPostHttp1() throws Exception { + HttpAddress httpAddress = HttpAddress.http1("localhost", 8008); + NamedServer namedServer = NamedServer.builder(httpAddress) + .singleEndpoint("/post", "/**", (req, resp) -> { + HttpParameters parameters = req.getParameters(); + logger.log(Level.INFO, "got post " + parameters.toString()); + resp.write(HttpResponseStatus.OK); + }, "POST") + .build(); + Server server = Server.builder(namedServer) + .build(); + Client client = Client.builder() + .build(); + final AtomicBoolean success = new AtomicBoolean(false); + try { + server.accept(); + Request request = Request.post().setVersion(HttpVersion.HTTP_1_1) + .url(server.getServerConfig().getAddress().base().resolve("/post/test.txt")) + .addParameter("a", "b") + .addFormParameter("name", "Jörg") + .build() + .setResponseListener(r -> { + if (r.status().equals(HttpResponseStatus.OK)) { + success.set(true); + } + }); + client.execute(request).get(); + logger.log(Level.INFO, "request complete"); + } finally { + server.shutdownGracefully(); + client.shutdownGracefully(); + logger.log(Level.INFO, "server and client shut down"); + } + assertTrue(success.get()); + } + + + @Test + void testPostHttp2() throws Exception { + HttpAddress httpAddress = HttpAddress.http2("localhost", 8008); + NamedServer namedServer = NamedServer.builder(httpAddress) + .singleEndpoint("/post", "/**", (req, resp) -> { + HttpParameters parameters = req.getParameters(); + logger.log(Level.INFO, "got post " + parameters.toString()); + resp.write(HttpResponseStatus.OK); + }, "POST") + .build(); + Server server = Server.builder(namedServer) + .build(); + Client client = Client.builder() + .build(); + final AtomicBoolean success = new AtomicBoolean(false); + try { + server.accept(); + Request request = Request.post().setVersion("HTTP/2.0") + .url(server.getServerConfig().getAddress().base().resolve("/post/test.txt")) + .addParameter("a", "b") + .addFormParameter("name", "Jörg") + .build() + .setResponseListener(r -> { + if (r.status().equals(HttpResponseStatus.OK)) { + success.set(true); + } + }); + client.execute(request).get(); + logger.log(Level.INFO, "request complete"); + } finally { + server.shutdownGracefully(); + client.shutdownGracefully(); + logger.log(Level.INFO, "server and client shut down"); + } + assertTrue(success.get()); + } +} diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureStaticFileServerTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureStaticFileServiceTest.java similarity index 98% rename from netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureStaticFileServerTest.java rename to netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureStaticFileServiceTest.java index f847e8e..529061e 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureStaticFileServerTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/SecureStaticFileServiceTest.java @@ -22,9 +22,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @ExtendWith(NettyHttpExtension.class) -class SecureStaticFileServerTest { +class SecureStaticFileServiceTest { - private static final Logger logger = Logger.getLogger(SecureStaticFileServerTest.class.getName()); + private static final Logger logger = Logger.getLogger(SecureStaticFileServiceTest.class.getName()); @Test void testSecureStaticFileServerHttp1() throws Exception { diff --git a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/StaticFileServerTest.java b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/StaticFileServiceTest.java similarity index 98% rename from netty-http-server/src/test/java/org/xbib/netty/http/server/test/StaticFileServerTest.java rename to netty-http-server/src/test/java/org/xbib/netty/http/server/test/StaticFileServiceTest.java index 536626a..cc98978 100644 --- a/netty-http-server/src/test/java/org/xbib/netty/http/server/test/StaticFileServerTest.java +++ b/netty-http-server/src/test/java/org/xbib/netty/http/server/test/StaticFileServiceTest.java @@ -22,9 +22,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @ExtendWith(NettyHttpExtension.class) -class StaticFileServerTest { +class StaticFileServiceTest { - private static final Logger logger = Logger.getLogger(StaticFileServerTest.class.getName()); + private static final Logger logger = Logger.getLogger(StaticFileServiceTest.class.getName()); @Test void testStaticFileServerHttp1() throws Exception { diff --git a/netty-http-server/src/test/resources/cl/test.txt b/netty-http-server/src/test/resources/cl/test.txt new file mode 100644 index 0000000..b51bde1 --- /dev/null +++ b/netty-http-server/src/test/resources/cl/test.txt @@ -0,0 +1 @@ +Hello Jörg \ No newline at end of file