diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml deleted file mode 100644 index 14a1edc..0000000 --- a/config/checkstyle/checkstyle.xml +++ /dev/null @@ -1,321 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/gradle.properties b/gradle.properties index 97d5d74..f0e6625 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group = org.xbib name = net-http -version = 3.3.0 +version = 3.3.1 org.gradle.warning.mode = ALL diff --git a/gradle/quality/pmd/category/java/codestyle.xml b/gradle/quality/pmd/category/java/codestyle.xml index ac2f0a0..186ea4b 100644 --- a/gradle/quality/pmd/category/java/codestyle.xml +++ b/gradle/quality/pmd/category/java/codestyle.xml @@ -760,7 +760,7 @@ public class HelloWorldBean { diff --git a/gradle/quality/pmd/category/java/errorprone.xml b/gradle/quality/pmd/category/java/errorprone.xml index cf289c3..5ee4e89 100644 --- a/gradle/quality/pmd/category/java/errorprone.xml +++ b/gradle/quality/pmd/category/java/errorprone.xml @@ -158,7 +158,7 @@ public class A { message="Avoid using a branching statement as the last in a loop." externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_errorprone.html#avoidbranchingstatementaslastinloop"> - Using a branching statement as the last part of a loop may be a bug, and/or is confusing. + Using a branching statement as the last message of a loop may be a bug, and/or is confusing. Ensure that the usage is not a bug, or consider using another approach. 2 @@ -1554,7 +1554,7 @@ public class Foo { diff --git a/net-http-client-netty-secure/src/main/java/org/xbib/net/http/client/netty/secure/HttpsRequestBuilder.java b/net-http-client-netty-secure/src/main/java/org/xbib/net/http/client/netty/secure/HttpsRequestBuilder.java index 9c02eb7..f731aaa 100644 --- a/net-http-client-netty-secure/src/main/java/org/xbib/net/http/client/netty/secure/HttpsRequestBuilder.java +++ b/net-http-client-netty-secure/src/main/java/org/xbib/net/http/client/netty/secure/HttpsRequestBuilder.java @@ -16,7 +16,6 @@ public class HttpsRequestBuilder extends HttpRequestBuilder { } public HttpsRequest build() { - this.headers = validateHeaders(headers); return new HttpsRequest(this); } } diff --git a/net-http-client-netty-secure/src/main/java/org/xbib/net/http/client/netty/secure/NettyHttpsClientConfig.java b/net-http-client-netty-secure/src/main/java/org/xbib/net/http/client/netty/secure/NettyHttpsClientConfig.java index 1998260..f480e41 100644 --- a/net-http-client-netty-secure/src/main/java/org/xbib/net/http/client/netty/secure/NettyHttpsClientConfig.java +++ b/net-http-client-netty-secure/src/main/java/org/xbib/net/http/client/netty/secure/NettyHttpsClientConfig.java @@ -6,7 +6,6 @@ import io.netty.util.AttributeKey; import java.io.InputStream; import java.security.KeyStore; import java.security.KeyStoreException; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; import javax.net.ssl.TrustManagerFactory; diff --git a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/BaseInteraction.java b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/BaseInteraction.java index 1fafd96..9dfcca1 100644 --- a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/BaseInteraction.java +++ b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/BaseInteraction.java @@ -270,7 +270,8 @@ public abstract class BaseInteraction implements Interaction { protected abstract Channel nextChannel() throws IOException; - protected HttpRequest continuation(HttpRequest request, HttpResponse httpResponse) throws URLSyntaxException { + protected HttpRequest continuation(HttpRequest request, + HttpResponse httpResponse) throws URLSyntaxException { if (httpResponse == null) { return null; } @@ -282,13 +283,7 @@ public abstract class BaseInteraction implements Interaction { if (request.canRedirect()) { int status = httpResponse.getStatus().code(); switch (status) { - case 300: - case 301: - case 302: - case 303: - case 305: - case 307: - case 308: + case 300, 301, 302, 303, 305, 307, 308 -> { String location = httpResponse.getHeaders().get(HttpHeaderNames.LOCATION); location = new PercentDecoder(StandardCharsets.UTF_8.newDecoder()).decode(location); if (location != null) { @@ -298,7 +293,7 @@ public abstract class BaseInteraction implements Interaction { HttpRequestBuilder newHttpRequestHttpRequestBuilder = HttpRequest.builder(method, request) .setURL(redirUrl); request.getURL().getQueryParams().forEach(pair -> - newHttpRequestHttpRequestBuilder.addParameter(pair.getKey(), pair.getValue()) + newHttpRequestHttpRequestBuilder.addParameter(pair.getKey(), pair.getValue()) ); request.cookies().forEach(newHttpRequestHttpRequestBuilder::addCookie); HttpRequest newHttpRequest = newHttpRequestHttpRequestBuilder.build(); @@ -311,9 +306,9 @@ public abstract class BaseInteraction implements Interaction { logger.log(Level.FINE, "redirect url: " + redirUrl); return newHttpRequest; } - break; - default: - break; + } + default -> { + } } } } catch (MalformedInputException | UnmappableCharacterException e) { @@ -331,20 +326,13 @@ public abstract class BaseInteraction implements Interaction { // push promise or something else return null; } - if (request.isBackOff()) { + if (request.isBackOffEnabled()) { BackOff backOff = request.getBackOff() != null ? request.getBackOff() : nettyHttpClient.getClientConfig().getBackOff(); int status = httpResponse.getStatus ().code(); switch (status) { - case 403: - case 404: - case 500: - case 502: - case 503: - case 504: - case 507: - case 509: + case 403, 404, 500, 502, 503, 504, 507, 509 -> { if (backOff != null) { long millis = backOff.nextBackOffMillis(); if (millis != BackOff.STOP) { @@ -357,9 +345,9 @@ public abstract class BaseInteraction implements Interaction { return request; } } - break; - default: - break; + } + default -> { + } } } return null; diff --git a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/HttpRequest.java b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/HttpRequest.java index 9223197..a5ab0a5 100644 --- a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/HttpRequest.java +++ b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/HttpRequest.java @@ -2,208 +2,20 @@ package org.xbib.net.http.client.netty; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.PooledByteBufAllocator; -import java.io.Closeable; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import org.xbib.net.ParameterBuilder; -import org.xbib.net.Request; -import org.xbib.net.URL; -import org.xbib.net.http.HttpHeaders; +import io.netty.handler.codec.http2.HttpConversionUtil; import org.xbib.net.http.HttpMethod; -import org.xbib.net.http.HttpVersion; -import org.xbib.net.http.client.BackOff; -import org.xbib.net.http.client.ExceptionListener; -import org.xbib.net.http.client.HttpResponse; -import org.xbib.net.http.client.Part; -import org.xbib.net.http.client.ResponseListener; -import org.xbib.net.http.client.TimeoutListener; -import org.xbib.net.http.cookie.Cookie; +import org.xbib.net.http.client.BaseHttpRequest; /** - * HTTP client request. + * Netty HTTP client request. */ -public class HttpRequest implements org.xbib.net.http.client.HttpRequest, Closeable { - - private final HttpRequestBuilder builder; - - private CompletableFuture completableFuture; - - private int redirectCount; +public class HttpRequest extends BaseHttpRequest { protected HttpRequest(HttpRequestBuilder builder) { - this.builder = builder; - } - - @Override - public URL getURL() { - return builder.url; - } - - @Override - public HttpVersion getVersion() { - return builder.httpVersion; - } - - @Override - public HttpMethod getMethod() { - return builder.httpMethod; - } - - @Override - public HttpHeaders getHeaders() { - return builder.headers; - } - - @Override - public ParameterBuilder getParameters() { - return builder.parameterBuilder; - } - - public Collection cookies() { - return builder.cookies; - } - - @Override - public InetSocketAddress getLocalAddress() { - return null; // unused - } - - @Override - public InetSocketAddress getRemoteAddress() { - return null; // unused - } - - @Override - public URL getBaseURL() { - return builder.url; - } - - public ByteBuffer getBody() { - return builder.body; - } - - @Override - public CharBuffer getBodyAsChars(Charset charset) { - return charset.decode(builder.body); - } - - public CharBuffer getBodyAsChars(Charset charset, int offset, int size) { - ByteBuffer slicedBuffer = (builder.body.duplicate().position(offset)).slice(); - slicedBuffer.limit(size); - return charset.decode(slicedBuffer); - } - - @SuppressWarnings("unchecked") - @Override - public R as(Class cl) { - return (R) this; - } - - public List getParts() { - return builder.parts; - } - - public boolean isFollowRedirect() { - return builder.followRedirect; - } - - public boolean isBackOff() { - return builder.backOff != null; - } - - public BackOff getBackOff() { - return builder.backOff; - } - - public boolean canRedirect() { - if (!builder.followRedirect) { - return false; - } - if (redirectCount >= builder.maxRedirects) { - return false; - } - redirectCount++; - return true; - } - - public void release() { - // nothing to do - } - - @Override - public void close() throws IOException { - release(); - } - - @Override - public String toString() { - return "HttpNettyRequest[url=" + builder.url + - ",version=" + builder.httpVersion + - ",method=" + builder.httpMethod + - ",headers=" + builder.headers.entries() + - ",content=" + (builder.body != null && builder.body.remaining() >= 16 ? - getBodyAsChars(StandardCharsets.UTF_8, 0, 16) + "..." : - builder.body != null ? getBodyAsChars(StandardCharsets.UTF_8) : "") + - "]"; - } - - public HttpRequest setCompletableFuture(CompletableFuture completableFuture) { - this.completableFuture = completableFuture; - return this; - } - - public CompletableFuture getCompletableFuture() { - return completableFuture; - } - - public void setResponseListener(ResponseListener responseListener) { - builder.responseListener = responseListener; - } - - public void onResponse(HttpResponse httpResponse) { - if (builder.responseListener != null) { - builder.responseListener.onResponse(httpResponse); - } - if (completableFuture != null) { - completableFuture.complete(this); - } - } - - public void setExceptionListener(ExceptionListener exceptionListener) { - builder.exceptionListener = exceptionListener; - } - - public void onException(Throwable throwable) { - if (builder.exceptionListener != null) { - builder.exceptionListener.onException(throwable); - } - if (completableFuture != null) { - completableFuture.completeExceptionally(throwable); - } - } - - public void setTimeoutListener(TimeoutListener timeoutListener) { - builder.timeoutListener = timeoutListener; - } - - public void onTimeout() { - if (builder.timeoutListener != null) { - builder.timeoutListener.onTimeout(this); - } - if (completableFuture != null) { - if (builder.timeoutMillis > 0L) { - completableFuture.completeOnTimeout(this, builder.timeoutMillis, TimeUnit.MILLISECONDS); - } else { - completableFuture.completeOnTimeout(this, 15L, TimeUnit.SECONDS); - } + super(builder); + String scheme = builder.getUrl().getScheme(); + if (this.builder.getVersion().majorVersion() == 2) { + this.builder.addHeader(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text().toString(), scheme); } } @@ -247,18 +59,19 @@ public class HttpRequest implements org.xbib.net.http.client.HttpRequest, Closea return builder(PooledByteBufAllocator.DEFAULT, httpMethod); } - public static HttpRequestBuilder builder(HttpMethod httpMethod, HttpRequest httpRequest) { - return builder(PooledByteBufAllocator.DEFAULT, httpMethod) - .setVersion(httpRequest.builder.httpVersion) - .setURL(httpRequest.builder.url) - .setHeaders(httpRequest.builder.headers) - .content(httpRequest.builder.body) - .setResponseListener(httpRequest.builder.responseListener) - .setTimeoutListener(httpRequest.builder.timeoutListener, httpRequest.builder.timeoutMillis) - .setExceptionListener(httpRequest.builder.exceptionListener); + public static HttpRequestBuilder builder(ByteBufAllocator allocator, HttpMethod httpMethod) { + return new HttpRequestBuilder(allocator) + .setMethod(httpMethod); } - public static HttpRequestBuilder builder(ByteBufAllocator allocator, HttpMethod httpMethod) { - return new HttpRequestBuilder(allocator).setMethod(httpMethod); + public static HttpRequestBuilder builder(HttpMethod httpMethod, HttpRequest httpRequest) { + return builder(PooledByteBufAllocator.DEFAULT, httpMethod) + .setVersion(httpRequest.getVersion()) + .setURL(httpRequest.getURL()) + .setHeaders(httpRequest.getHeaders()) + .content(httpRequest.getBody()) + .setResponseListener(httpRequest.getResponseListener()) + .setTimeoutListener(httpRequest.getTimeoutListener(), httpRequest.getTimeoutMillis()) + .setExceptionListener(httpRequest.getExceptionListener()); } } diff --git a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/HttpRequestBuilder.java b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/HttpRequestBuilder.java index 7e55936..90f326f 100644 --- a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/HttpRequestBuilder.java +++ b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/HttpRequestBuilder.java @@ -2,202 +2,87 @@ package org.xbib.net.http.client.netty; import io.netty.buffer.ByteBufAllocator; import io.netty.handler.codec.http.HttpUtil; -import io.netty.handler.codec.http2.HttpConversionUtil; -import java.io.IOException; -import java.io.UncheckedIOException; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; import java.util.Map; -import java.util.Objects; -import org.xbib.net.Parameter; import org.xbib.net.ParameterBuilder; import org.xbib.net.URL; -import org.xbib.net.URLBuilder; import org.xbib.net.http.HttpAddress; -import org.xbib.net.http.HttpHeaderNames; -import org.xbib.net.http.HttpHeaderValues; import org.xbib.net.http.HttpHeaders; import org.xbib.net.http.HttpMethod; import org.xbib.net.http.HttpVersion; -import org.xbib.net.http.client.BackOff; +import org.xbib.net.http.client.BaseHttpRequestBuilder; import org.xbib.net.http.client.ExceptionListener; -import org.xbib.net.http.client.HttpResponse; -import org.xbib.net.http.client.Part; +import org.xbib.net.http.client.Message; import org.xbib.net.http.client.ResponseListener; import org.xbib.net.http.client.TimeoutListener; -import org.xbib.net.http.cookie.Cookie; -public class HttpRequestBuilder implements org.xbib.net.http.client.HttpRequestBuilder { - - private static final URL DEFAULT_URL = URL.from("http://localhost"); - - private static final String DEFAULT_FORM_CONTENT_TYPE = "application/x-www-form-urlencoded; charset=utf-8"; +public class HttpRequestBuilder extends BaseHttpRequestBuilder { protected final ByteBufAllocator allocator; - protected HttpAddress httpAddress; - - protected URL url; - - protected String requestPath; - - protected final Collection cookies; - - protected HttpMethod httpMethod; - - protected HttpHeaders headers; - - protected HttpVersion httpVersion; - - protected final List removeHeaders; - - protected String userAgent; - - protected boolean keepalive; - - protected boolean gzip; - - protected String contentType; - - protected ParameterBuilder parameterBuilder; - - protected ByteBuffer body; - - protected boolean followRedirect; - - protected int maxRedirects; - - protected boolean enableBackOff; - - protected BackOff backOff; - - protected ResponseListener responseListener; - - protected ExceptionListener exceptionListener; - - protected TimeoutListener timeoutListener; - - protected long timeoutMillis; - - protected final List parts; - protected HttpRequestBuilder() { this(ByteBufAllocator.DEFAULT); } protected HttpRequestBuilder(ByteBufAllocator allocator) { + super(); this.allocator = allocator; - this.httpMethod = HttpMethod.GET; - this.httpVersion = HttpVersion.HTTP_1_1; this.userAgent = UserAgent.getUserAgent(); - this.gzip = false; - this.keepalive = true; - this.url = DEFAULT_URL; - this.followRedirect = true; - this.maxRedirects = 10; - this.headers = new HttpHeaders(); - this.removeHeaders = new ArrayList<>(); - this.cookies = new HashSet<>(); - this.contentType = DEFAULT_FORM_CONTENT_TYPE; - this.parameterBuilder = Parameter.builder(); - this.timeoutMillis = 0L; - this.parts = new ArrayList<>(); } @Override public HttpRequestBuilder setAddress(HttpAddress httpAddress) { - this.httpAddress = httpAddress; - try { - this.url = URL.builder() - .scheme(httpAddress.isSecure() ? "https" : "http") - .host(httpAddress.getInetSocketAddress().getHostString()) - .port(httpAddress.getInetSocketAddress().getPort()) - .build(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - this.httpVersion = httpAddress.getVersion(); + super.setAddress(httpAddress); return this; } - public HttpRequestBuilder setURL(String url) { - return setURL(URL.from(url)); - } - @Override public HttpRequestBuilder setURL(URL url) { - this.url = url; + super.setURL(url); + return this; + } + + @Override + public HttpRequestBuilder setURL(String url) { + super.setURL(url); + return this; + } + + @Override + public HttpRequestBuilder setMethod(HttpMethod httpMethod) { + super.setMethod(httpMethod); + return this; + } + + @Override + public HttpRequestBuilder setVersion(HttpVersion httpVersion) { + super.setVersion(httpVersion); + return this; + } + + @Override + public HttpRequestBuilder setVersion(String httpVersion) { + super.setVersion(httpVersion); return this; } @Override public HttpRequestBuilder setRequestPath(String requestPath) { - this.requestPath = requestPath; + super.setRequestPath(requestPath); return this; } - public HttpRequestBuilder setMethod(HttpMethod httpMethod) { - this.httpMethod = httpMethod; - return this; - } - - public HttpRequestBuilder setVersion(HttpVersion httpVersion) { - this.httpVersion = httpVersion; - return this; - } - - public HttpRequestBuilder setVersion(String httpVersion) { - this.httpVersion = HttpVersion.valueOf(httpVersion); - return this; - } - - public HttpRequestBuilder setHeaders(Map headers) { - headers.forEach(this::addHeader); - return this; - } - - public HttpRequestBuilder setHeaders(HttpHeaders headers) { - this.headers = headers; + @Override + public HttpRequestBuilder setHeaders(HttpHeaders httpHeaders) { + super.setHeaders(httpHeaders); return this; } + @Override public HttpRequestBuilder addHeader(String name, String value) { - this.headers.add(name, value); - return this; - } - - public HttpRequestBuilder setHeader(String name, String value) { - this.headers.set(name, value); - return this; - } - - public HttpRequestBuilder removeHeader(String name) { - removeHeaders.add(name); - return this; - } - - public HttpRequestBuilder contentType(String contentType) { - Objects.requireNonNull(contentType); - this.contentType = contentType; - addHeader(HttpHeaderNames.CONTENT_TYPE, contentType); - return this; - } - - public HttpRequestBuilder contentType(String contentType, Charset charset) { - Objects.requireNonNull(contentType); - Objects.requireNonNull(charset); - this.contentType = contentType; - addHeader(HttpHeaderNames.CONTENT_TYPE, contentType + "; charset=" + charset.name().toLowerCase()); + super.addHeader(name, value); return this; } @@ -207,114 +92,63 @@ public class HttpRequestBuilder implements org.xbib.net.http.client.HttpRequestB return this; } - public HttpRequestBuilder setParameters(Map parameters) { - parameters.forEach(this::addParameter); + @Override + public HttpRequestBuilder setParameters(Map map) { + super.setParameters(map); return this; } - @SuppressWarnings("unchecked") + @Override public HttpRequestBuilder addParameter(String name, Object value) { - Objects.requireNonNull(name); - Objects.requireNonNull(value); - Collection collection; - if (!(value instanceof Collection)) { - collection = Collections.singletonList(value); - } else { - collection = (Collection) value; - } - collection.forEach(v -> parameterBuilder.add(name, v)); - return this; - } - - public HttpRequestBuilder addRawParameter(String name, String value) { - Objects.requireNonNull(name); - Objects.requireNonNull(value); - parameterBuilder.add(name, value); - return this; - } - - public HttpRequestBuilder addBasicAuthorization(String name, String password) { - String encoding = Base64.getEncoder().encodeToString((name + ":" + password).getBytes(StandardCharsets.UTF_8)); - this.headers.add(HttpHeaderNames.AUTHORIZATION, "Basic " + encoding); + super.addParameter(name, value); return this; } @Override public HttpRequestBuilder setBody(ByteBuffer byteBuffer) { - this.body = byteBuffer; + super.setBody(byteBuffer); return this; } @Override - public HttpRequestBuilder addPart(Part part) { - parts.add(part); + public HttpRequestBuilder addMessage(Message message) { + super.addMessage(message); return this; } - public HttpRequestBuilder addCookie(Cookie cookie) { - cookies.add(cookie); + @Override + public HttpRequestBuilder content(ByteBuffer byteBuffer) { + super.content(byteBuffer); return this; } - public HttpRequestBuilder acceptGzip(boolean gzip) { - this.gzip = gzip; - return this; - } - - public HttpRequestBuilder keepAlive(boolean keepalive) { - this.keepalive = keepalive; + @Override + public HttpRequestBuilder content(CharSequence charSequence, CharSequence contentType, Charset charset) { + super.content(charSequence, contentType, charset); return this; } + @Override public HttpRequestBuilder setFollowRedirect(boolean followRedirect) { - this.followRedirect = followRedirect; + super.setFollowRedirect(followRedirect); return this; } - public HttpRequestBuilder setMaxRedirects(int maxRedirects) { - this.maxRedirects = maxRedirects; + @Override + public HttpRequestBuilder setResponseListener(ResponseListener responseListener) { + super.setResponseListener(responseListener); return this; } - public HttpRequestBuilder enableBackOff(boolean enableBackOff) { - this.enableBackOff = enableBackOff; + @Override + public HttpRequestBuilder setExceptionListener(ExceptionListener exceptionListener) { + super.setExceptionListener(exceptionListener); return this; } - public HttpRequestBuilder setBackOff(BackOff backOff) { - this.backOff = backOff; - return this; - } - - public HttpRequestBuilder setUserAgent(String userAgent) { - this.userAgent = userAgent; - return this; - } - - public HttpRequestBuilder text(String text) { - if (text == null) { - return this; - } - ByteBuffer byteBuf = StandardCharsets.UTF_8.encode(text); - content(byteBuf, HttpHeaderValues.TEXT_PLAIN); - return this; - } - - public HttpRequestBuilder json(String json) { - if (json == null) { - return this; - } - ByteBuffer byteBuf = StandardCharsets.UTF_8.encode(json); - content(byteBuf, HttpHeaderValues.APPLICATION_JSON); - return this; - } - - public HttpRequestBuilder xml(String xml) { - if (xml == null) { - return this; - } - ByteBuffer byteBuf = StandardCharsets.UTF_8.encode(xml); - content(byteBuf, "application/xml"); + @Override + public HttpRequestBuilder setTimeoutListener(TimeoutListener timeoutListener, long timeoutMillis) { + super.setTimeoutListener(timeoutListener, timeoutMillis); return this; } @@ -322,113 +156,12 @@ public class HttpRequestBuilder implements org.xbib.net.http.client.HttpRequestB if (charSequence == null) { return this; } + // use current content type charset or UTF-8 content(charSequence.toString().getBytes(HttpUtil.getCharset(contentType, StandardCharsets.UTF_8)), contentType.toString()); return this; } - public HttpRequestBuilder content(CharSequence charSequence, CharSequence contentType, Charset charset) { - if (charSequence == null) { - return this; - } - content(charSequence.toString().getBytes(charset), contentType.toString()); - return this; - } - - public HttpRequestBuilder content(byte[] buf, String contentType) { - if (buf == null) { - return this; - } - content(ByteBuffer.wrap(buf), contentType); - return this; - } - - public HttpRequestBuilder content(ByteBuffer content, String contentType) { - if (content == null) { - return this; - } - setBody(content); - addHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(content.remaining())); - addHeader(HttpHeaderNames.CONTENT_TYPE, contentType); - return this; - } - - public HttpRequestBuilder content(ByteBuffer content) { - if (content == null) { - return this; - } - this.body = content; - return this; - } - - public HttpRequestBuilder setResponseListener(ResponseListener responseListener) { - this.responseListener = responseListener; - return this; - } - - public HttpRequestBuilder setExceptionListener(ExceptionListener exceptionListener) { - this.exceptionListener = exceptionListener; - return this; - } - - public HttpRequestBuilder setTimeoutListener(TimeoutListener timeoutListener, long timeoutMillis) { - this.timeoutListener = timeoutListener; - this.timeoutMillis = timeoutMillis; - return this; - } - public HttpRequest build() { - this.headers = validateHeaders(headers); return new HttpRequest(this); } - - protected HttpHeaders validateHeaders(HttpHeaders httpHeaders) { - Parameter parameter = parameterBuilder.build(); - HttpHeaders validatedHeaders = HttpHeaders.of(headers); - if (url != null) { - // add our URI parameters to the URL - URLBuilder urlBuilder = url.mutator(); - if (requestPath != null) { - urlBuilder.path(requestPath); - } - parameter.forEach(e -> urlBuilder.queryParam(e.getKey(), e.getValue())); - url = urlBuilder.build(); - 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"); - } - if (httpMethod.name().equals(HttpMethod.POST.name())) { - content(parameter.getAsQueryString(), contentType); - } - int length = body != null ? body.remaining() : 0; - if (!validatedHeaders.containsHeader(HttpHeaderNames.CONTENT_LENGTH) && !validatedHeaders.containsHeader(HttpHeaderNames.TRANSFER_ENCODING)) { - if (length < 0) { - validatedHeaders.set(HttpHeaderNames.TRANSFER_ENCODING, "chunked"); - } else { - validatedHeaders.set(HttpHeaderNames.CONTENT_LENGTH, Long.toString(length)); - } - } - if (!validatedHeaders.containsHeader(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 validatedHeaders; - } } diff --git a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/NettyHttpClientConfig.java b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/NettyHttpClientConfig.java index 771284d..ae0bb7b 100644 --- a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/NettyHttpClientConfig.java +++ b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/NettyHttpClientConfig.java @@ -91,7 +91,7 @@ public class NettyHttpClientConfig { private HttpVersion poolVersion = HttpVersion.HTTP_1_1; - private Boolean poolSecure = false; + private boolean poolSecure = false; private Http2Settings http2Settings = Http2Settings.defaultSettings(); @@ -99,9 +99,11 @@ public class NettyHttpClientConfig { private BackOff backOff = BackOff.ZERO_BACKOFF; - private Boolean isChunkWriteEnabled = true; + private boolean isChunkWriteEnabled = true; - private Boolean isObjectAggregationEnabled = true; + private boolean isObjectAggregationEnabled = true; + + private boolean isFileUploadEnabled = true; public NettyHttpClientConfig() { this.byteBufAllocator = ByteBufAllocator.DEFAULT; @@ -343,7 +345,7 @@ public class NettyHttpClientConfig { return this; } - public Boolean isChunkWriteEnabled() { + public boolean isChunkWriteEnabled() { return isChunkWriteEnabled; } @@ -352,7 +354,16 @@ public class NettyHttpClientConfig { return this; } - public Boolean isObjectAggregationEnabled() { + public boolean isObjectAggregationEnabled() { return isObjectAggregationEnabled; } + + public NettyHttpClientConfig setFileUploadEnabled(boolean fileUploadEnabled) { + isFileUploadEnabled = fileUploadEnabled; + return this; + } + + public boolean isFileUploadEnabled() { + return isFileUploadEnabled; + } } diff --git a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/http1/Http1Interaction.java b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/http1/Http1Interaction.java index c510267..8f7409b 100644 --- a/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/http1/Http1Interaction.java +++ b/net-http-client-netty/src/main/java/org/xbib/net/http/client/netty/http1/Http1Interaction.java @@ -30,7 +30,7 @@ import org.xbib.net.URLSyntaxException; import org.xbib.net.http.HttpAddress; import org.xbib.net.http.HttpHeaders; import org.xbib.net.http.HttpResponseStatus; -import org.xbib.net.http.client.Part; +import org.xbib.net.http.client.Message; import org.xbib.net.http.client.netty.NettyHttpClientConfig; import org.xbib.net.http.cookie.Cookie; import org.xbib.net.http.client.cookie.CookieDecoder; @@ -105,40 +105,43 @@ public class Http1Interaction extends BaseInteraction { // headers request.getHeaders().entries().forEach(p -> fullHttpRequest.headers().add(p.getKey(), p.getValue())); // file upload - HttpDataFactory httpDataFactory = new DefaultHttpDataFactory(); - HttpPostRequestEncoder httpPostRequestEncoder = null; - try { - if (!request.getParts().isEmpty()) { - httpPostRequestEncoder = new HttpPostRequestEncoder(httpDataFactory, - fullHttpRequest, - true, - StandardCharsets.UTF_8, - HttpPostRequestEncoder.EncoderMode.RFC1738); - for (Part part : request.getParts()) { - Path path = part.getPath(); - if (Files.exists(path)) { - FileUpload fileUpload = httpDataFactory.createFileUpload(fullHttpRequest, part.getName(), - path.toFile().getName(), part.getContentType(), part.getContentTransferEncoding(), - part.getCharset(), Files.size(path)); - fileUpload.setContent(path.toFile()); - logger.log(Level.FINEST, "HTTP FORM file upload = " + fileUpload); - httpPostRequestEncoder.addBodyHttpData(fileUpload); - } else { - logger.log(Level.WARNING, " does not exist : " + path); - } - } - io.netty.handler.codec.http.HttpRequest httpRequest = httpPostRequestEncoder.finalizeRequest(); - channel.write(httpRequest); - } - channel.write(fullHttpRequest); - if (httpPostRequestEncoder != null && httpPostRequestEncoder.isChunked()) { - channel.write(httpPostRequestEncoder); - } - channel.flush(); - } catch (HttpPostRequestEncoder.ErrorDataEncoderException e) { - throw new IOException(e); - } finally { + if (nettyHttpClient.getClientConfig().isFileUploadEnabled()) { + HttpDataFactory httpDataFactory = new DefaultHttpDataFactory(); channel.attr(NettyHttpClientConfig.ATTRIBUTE_HTTP_DATAFACTORY).set(httpDataFactory); + HttpPostRequestEncoder httpPostRequestEncoder = null; + try { + if (!request.getMessages().isEmpty()) { + httpPostRequestEncoder = new HttpPostRequestEncoder(httpDataFactory, + fullHttpRequest, + true, + StandardCharsets.UTF_8, + HttpPostRequestEncoder.EncoderMode.RFC1738); + for (Message message : request.getMessages()) { + Path path = message.getPath(); + if (Files.exists(path)) { + FileUpload fileUpload = httpDataFactory.createFileUpload(fullHttpRequest, message.getName(), + path.toFile().getName(), message.getContentType(), message.getContentTransferEncoding(), + message.getCharset(), Files.size(path)); + fileUpload.setContent(path.toFile()); + logger.log(Level.FINEST, "HTTP FORM file upload = " + fileUpload); + httpPostRequestEncoder.addBodyHttpData(fileUpload); + } else { + logger.log(Level.WARNING, " does not exist : " + path); + } + } + io.netty.handler.codec.http.HttpRequest httpRequest = httpPostRequestEncoder.finalizeRequest(); + channel.write(httpRequest); + } + channel.write(fullHttpRequest); + if (httpPostRequestEncoder != null && httpPostRequestEncoder.isChunked()) { + channel.write(httpPostRequestEncoder); + } + channel.flush(); + } catch (HttpPostRequestEncoder.ErrorDataEncoderException e) { + throw new IOException(e); + } + } else { + channel.write(fullHttpRequest); } return this; } diff --git a/net-http-client/src/main/java/org/xbib/net/http/client/BaseHttpRequest.java b/net-http-client/src/main/java/org/xbib/net/http/client/BaseHttpRequest.java index 5606077..4fe1980 100644 --- a/net-http-client/src/main/java/org/xbib/net/http/client/BaseHttpRequest.java +++ b/net-http-client/src/main/java/org/xbib/net/http/client/BaseHttpRequest.java @@ -4,11 +4,24 @@ import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import org.xbib.net.Parameter; import org.xbib.net.ParameterBuilder; +import org.xbib.net.Request; import org.xbib.net.URL; +import org.xbib.net.URLBuilder; +import org.xbib.net.http.HttpHeaderNames; import org.xbib.net.http.HttpHeaders; import org.xbib.net.http.HttpMethod; import org.xbib.net.http.HttpVersion; +import org.xbib.net.http.cookie.Cookie; public abstract class BaseHttpRequest implements HttpRequest { @@ -16,6 +29,48 @@ public abstract class BaseHttpRequest implements HttpRequest { protected BaseHttpRequest(BaseHttpRequestBuilder builder) { this.builder = builder; + Parameter parameter = builder.parameterBuilder.build(); + // validate request + HttpHeaders validatedHeaders = HttpHeaders.of(builder.httpHeaders); + if (builder.url != null) { + // add our URI parameters to the URL + URLBuilder urlBuilder = builder.url.mutator(); + if (builder.requestPath != null) { + urlBuilder.path(builder.requestPath); + } + parameter.forEach(e -> urlBuilder.queryParam(e.getKey(), e.getValue())); + builder.url = urlBuilder.build(); + validatedHeaders.set(HttpHeaderNames.HOST, builder.url.getHostInfo()); + } + validatedHeaders.set(HttpHeaderNames.DATE, DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC))); + if (builder.userAgent != null) { + validatedHeaders.set(HttpHeaderNames.USER_AGENT, builder.userAgent); + } + if (builder.isGzipEnabled) { + validatedHeaders.set(HttpHeaderNames.ACCEPT_ENCODING, "gzip"); + } + if (builder.httpMethod.name().equals(HttpMethod.POST.name())) { + builder.content(parameter.getAsQueryString(), builder.contentType, StandardCharsets.ISO_8859_1); + } + if (!validatedHeaders.containsHeader(HttpHeaderNames.CONTENT_LENGTH) && + !validatedHeaders.containsHeader(HttpHeaderNames.TRANSFER_ENCODING)) { + int length = builder.byteBuffer != null ? builder.byteBuffer.remaining() : 0; + if (length < 0) { + validatedHeaders.set(HttpHeaderNames.TRANSFER_ENCODING, "chunked"); + } else { + validatedHeaders.set(HttpHeaderNames.CONTENT_LENGTH, Long.toString(length)); + } + } + if (!validatedHeaders.containsHeader(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 (builder.httpVersion.majorVersion() == 1 && !builder.isKeepAliveEnabled) { + validatedHeaders.set(HttpHeaderNames.CONNECTION, "close"); + } + builder.setHeaders(validatedHeaders); } @Override @@ -30,6 +85,15 @@ public abstract class BaseHttpRequest implements HttpRequest { @Override public URL getBaseURL() { + return URL.builder() + .scheme(builder.url.getScheme()) + .host(builder.url.getHost()) + .port(builder.url.getPort()) + .build(); + } + + @Override + public URL getURL() { return builder.url; } @@ -49,7 +113,7 @@ public abstract class BaseHttpRequest implements HttpRequest { } @Override - public ParameterBuilder getParameters() { + public ParameterBuilder getParameterBuilder() { return builder.parameterBuilder; } @@ -62,4 +126,128 @@ public abstract class BaseHttpRequest implements HttpRequest { public CharBuffer getBodyAsChars(Charset charset) { return builder.byteBuffer != null ? charset.decode(builder.byteBuffer) : null; } + + public CharBuffer getBodyAsChars(Charset charset, int offset, int size) { + ByteBuffer slicedBuffer = (builder.byteBuffer.position(offset)).slice(); + slicedBuffer.limit(size); + return charset.decode(slicedBuffer); + } + + @Override + public List getMessages() { + return builder.messages; + } + + @SuppressWarnings("unchecked") + @Override + public R as(Class cl) { + return (R) this; + } + + public boolean canRedirect() { + if (!builder.followRedirect) { + return false; + } + if (builder.redirectCount >= builder.maxRedirects) { + return false; + } + builder.redirectCount++; + return true; + } + + @Override + public boolean isBackOffEnabled() { + return builder.isBackoffEnabled; + } + + @Override + public BackOff getBackOff() { + return builder.backOff; + } + + @Override + public Collection cookies() { + return builder.cookies; + } + + public HttpRequest setCompletableFuture(CompletableFuture completableFuture) { + builder.completableFuture = completableFuture; + return this; + } + + public CompletableFuture getCompletableFuture() { + return builder.completableFuture; + } + + public void setResponseListener(ResponseListener responseListener) { + builder.responseListener = responseListener; + } + + public ResponseListener getResponseListener() { + return builder.responseListener; + } + + public void onResponse(HttpResponse httpResponse) { + if (builder.responseListener != null) { + builder.responseListener.onResponse(httpResponse); + } + if (builder.completableFuture != null) { + builder.completableFuture.complete(this); + } + } + + public void setExceptionListener(ExceptionListener exceptionListener) { + builder.exceptionListener = exceptionListener; + } + + public ExceptionListener getExceptionListener() { + return builder.exceptionListener; + } + + public void onException(Throwable throwable) { + if (builder.exceptionListener != null) { + builder.exceptionListener.onException(throwable); + } + if (builder.completableFuture != null) { + builder.completableFuture.completeExceptionally(throwable); + } + } + + public void setTimeoutListener(TimeoutListener timeoutListener) { + builder.timeoutListener = timeoutListener; + } + + public TimeoutListener getTimeoutListener() { + return builder.timeoutListener; + } + + public void onTimeout() { + if (builder.timeoutListener != null) { + builder.timeoutListener.onTimeout(this); + } + if (builder.completableFuture != null) { + if (builder.timeoutMillis > 0L) { + builder.completableFuture.completeOnTimeout(this, builder.timeoutMillis, TimeUnit.MILLISECONDS); + } else { + builder.completableFuture.completeOnTimeout(this, 15L, TimeUnit.SECONDS); + } + } + } + + public long getTimeoutMillis() { + return builder.timeoutMillis; + } + + @Override + public String toString() { + return "HttpRequest[url=" + builder.url + + ",version=" + builder.httpVersion + + ",method=" + builder.httpMethod + + ",headers=" + builder.httpHeaders.entries() + + ",content=" + (builder.byteBuffer != null && builder.byteBuffer.remaining() >= 16 ? + getBodyAsChars(StandardCharsets.UTF_8, 0, 16) + "..." : + builder.byteBuffer != null ? getBodyAsChars(StandardCharsets.UTF_8) : "") + + ",messages=" + builder.messages + + "]"; + } } diff --git a/net-http-client/src/main/java/org/xbib/net/http/client/BaseHttpRequestBuilder.java b/net-http-client/src/main/java/org/xbib/net/http/client/BaseHttpRequestBuilder.java index 0e153a0..bfe347f 100644 --- a/net-http-client/src/main/java/org/xbib/net/http/client/BaseHttpRequestBuilder.java +++ b/net-http-client/src/main/java/org/xbib/net/http/client/BaseHttpRequestBuilder.java @@ -2,42 +2,113 @@ package org.xbib.net.http.client; import java.net.InetSocketAddress; import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import org.xbib.net.Parameter; import org.xbib.net.ParameterBuilder; import org.xbib.net.URL; import org.xbib.net.http.HttpAddress; +import org.xbib.net.http.HttpHeaderNames; +import org.xbib.net.http.HttpHeaderValues; import org.xbib.net.http.HttpHeaders; import org.xbib.net.http.HttpMethod; import org.xbib.net.http.HttpVersion; +import org.xbib.net.http.cookie.Cookie; public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder { - HttpAddress httpAddress; + private static final URL DEFAULT_URL = URL.from("http://localhost"); - InetSocketAddress localAddress; + private static final String DEFAULT_FORM_CONTENT_TYPE = "application/x-www-form-urlencoded; charset=utf-8"; - InetSocketAddress remoteAddress; + /** + * The default value for {@code User-Agent}. + */ + private static final String DEFAULT_USER_AGENT = String.format("HttpClient/%s (Java/%s/%s)", + httpClientVersion(), javaVendor(), javaVersion()); - URL url; + protected HttpAddress httpAddress; - String requestPath; + protected InetSocketAddress localAddress; - ParameterBuilder parameterBuilder; + protected InetSocketAddress remoteAddress; - Integer sequenceId; + protected URL url; - Integer streamId; + protected String requestPath; - Long requestId; + protected ParameterBuilder parameterBuilder; - HttpVersion httpVersion; + protected Integer sequenceId; - HttpMethod httpMethod; + protected Integer streamId; - HttpHeaders httpHeaders = new HttpHeaders(); + protected Long requestId; - ByteBuffer byteBuffer; + protected HttpVersion httpVersion; + + protected HttpMethod httpMethod; + + protected HttpHeaders httpHeaders; + + protected ByteBuffer byteBuffer; + + protected List messages; + + protected boolean followRedirect; + + protected int maxRedirects; + + protected int redirectCount; + + protected final Collection cookies; + + protected String contentType; + + protected String userAgent; + + protected boolean isGzipEnabled; + + protected boolean isKeepAliveEnabled; + + protected boolean isBackoffEnabled; + + protected BackOff backOff; + + protected ResponseListener responseListener; + + protected ExceptionListener exceptionListener; + + protected TimeoutListener timeoutListener; + + protected long timeoutMillis; + + protected CompletableFuture completableFuture; protected BaseHttpRequestBuilder() { + this.httpMethod = HttpMethod.GET; + this.httpVersion = HttpVersion.HTTP_1_1; + this.url = DEFAULT_URL; + this.contentType = DEFAULT_FORM_CONTENT_TYPE; + this.userAgent = getUserAgent(); + this.isGzipEnabled = false; + this.isKeepAliveEnabled = true; + this.followRedirect = true; + this.maxRedirects = 10; + this.parameterBuilder = Parameter.builder(); + this.httpHeaders = new HttpHeaders(); + this.messages = new ArrayList<>(); + this.cookies = new HashSet<>(); } public BaseHttpRequestBuilder setVersion(HttpVersion httpVersion) { @@ -45,6 +116,11 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder { return this; } + public BaseHttpRequestBuilder setVersion(String httpVersion) { + setVersion(HttpVersion.valueOf(httpVersion)); + return this; + } + public HttpVersion getVersion() { return httpVersion; } @@ -63,22 +139,51 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder { return this; } + public BaseHttpRequestBuilder setHeaders(Map headers) { + headers.forEach(this::addHeader); + return this; + } + public BaseHttpRequestBuilder addHeader(String key, String value) { this.httpHeaders.add(key, value); return this; } + public BaseHttpRequestBuilder contentType(String contentType) { + Objects.requireNonNull(contentType); + this.contentType = contentType; + addHeader(HttpHeaderNames.CONTENT_TYPE, contentType); + return this; + } + + public BaseHttpRequestBuilder contentType(String contentType, Charset charset) { + Objects.requireNonNull(contentType); + Objects.requireNonNull(charset); + this.contentType = contentType; + addHeader(HttpHeaderNames.CONTENT_TYPE, contentType + "; charset=" + charset.name().toLowerCase()); + return this; + } + @Override public BaseHttpRequestBuilder setAddress(HttpAddress httpAddress) { this.httpAddress = httpAddress; return this; } + @Override public BaseHttpRequestBuilder setURL(URL url) { this.url = url; return this; } + public BaseHttpRequestBuilder setURL(String url) { + return setURL(URL.from(url)); + } + + public URL getUrl() { + return this.url; + } + @Override public BaseHttpRequestBuilder setRequestPath(String requestPath) { this.requestPath = requestPath; @@ -91,12 +196,50 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder { return this; } + public BaseHttpRequestBuilder setParameters(Map map) { + map.forEach(this::addParameter); + return this; + } + + @SuppressWarnings("unchecked") + public BaseHttpRequestBuilder addParameter(String name, Object value) { + Objects.requireNonNull(name); + Objects.requireNonNull(value); + Collection collection; + if (!(value instanceof Collection)) { + collection = Collections.singletonList(value); + } else { + collection = (Collection) value; + } + collection.forEach(v -> parameterBuilder.add(name, v)); + return this; + } + + public BaseHttpRequestBuilder addRawParameter(String name, String value) { + Objects.requireNonNull(name); + Objects.requireNonNull(value); + parameterBuilder.add(name, value); + return this; + } + + public BaseHttpRequestBuilder addBasicAuthorization(String name, String password) { + String encoding = Base64.getEncoder().encodeToString((name + ":" + password).getBytes(StandardCharsets.UTF_8)); + this.httpHeaders.add(HttpHeaderNames.AUTHORIZATION, "Basic " + encoding); + return this; + } + @Override public BaseHttpRequestBuilder setBody(ByteBuffer byteBuffer) { this.byteBuffer = byteBuffer; return this; } + @Override + public BaseHttpRequestBuilder addMessage(Message message) { + messages.add(message); + return this; + } + public BaseHttpRequestBuilder setLocalAddress(InetSocketAddress localAddress) { this.localAddress = localAddress; return this; @@ -121,4 +264,142 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder { this.requestId = requestId; return this; } + + public BaseHttpRequestBuilder setFollowRedirect(boolean followRedirect) { + this.followRedirect = followRedirect; + return this; + } + + public BaseHttpRequestBuilder setMaxRedirects(int maxRedirects) { + this.maxRedirects = maxRedirects; + return this; + } + + public BaseHttpRequestBuilder addCookie(Cookie cookie) { + cookies.add(cookie); + return this; + } + + public BaseHttpRequestBuilder isGzipEnabled(boolean isGzipEnabled) { + this.isGzipEnabled = isGzipEnabled; + return this; + } + + public BaseHttpRequestBuilder isKeepAliveEnabled(boolean keepalive) { + this.isKeepAliveEnabled = keepalive; + return this; + } + + public BaseHttpRequestBuilder isBackOffEnabled(boolean enableBackOff) { + this.isBackoffEnabled = enableBackOff; + return this; + } + + public BaseHttpRequestBuilder setBackOff(BackOff backOff) { + this.backOff = backOff; + return this; + } + + public BaseHttpRequestBuilder setUserAgent(String userAgent) { + this.userAgent = userAgent; + return this; + } + + public BaseHttpRequestBuilder text(String text) { + if (text == null) { + return this; + } + ByteBuffer byteBuf = StandardCharsets.UTF_8.encode(text); + content(byteBuf, HttpHeaderValues.TEXT_PLAIN); + return this; + } + + public BaseHttpRequestBuilder json(String json) { + if (json == null) { + return this; + } + ByteBuffer byteBuf = StandardCharsets.UTF_8.encode(json); + content(byteBuf, HttpHeaderValues.APPLICATION_JSON); + return this; + } + + public BaseHttpRequestBuilder xml(String xml) { + if (xml == null) { + return this; + } + ByteBuffer byteBuf = StandardCharsets.UTF_8.encode(xml); + content(byteBuf, "application/xml"); + return this; + } + + public BaseHttpRequestBuilder content(CharSequence charSequence, CharSequence contentType, Charset charset) { + if (charSequence == null) { + return this; + } + content(charSequence.toString().getBytes(charset), contentType.toString()); + return this; + } + + public BaseHttpRequestBuilder content(byte[] buf, String contentType) { + if (buf == null) { + return this; + } + content(ByteBuffer.wrap(buf), contentType); + return this; + } + + public BaseHttpRequestBuilder content(ByteBuffer content, String contentType) { + if (content == null) { + return this; + } + setBody(content); + addHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(content.remaining())); + if (contentType != null) { + addHeader(HttpHeaderNames.CONTENT_TYPE, contentType); + } + return this; + } + + public BaseHttpRequestBuilder content(ByteBuffer byteBuffer) { + if (byteBuffer == null) { + return this; + } + this.byteBuffer = byteBuffer; + return this; + } + + public BaseHttpRequestBuilder setResponseListener(ResponseListener responseListener) { + this.responseListener = responseListener; + return this; + } + + public BaseHttpRequestBuilder setExceptionListener(ExceptionListener exceptionListener) { + this.exceptionListener = exceptionListener; + return this; + } + + public BaseHttpRequestBuilder setTimeoutListener(TimeoutListener timeoutListener, long timeoutMillis) { + this.timeoutListener = timeoutListener; + this.timeoutMillis = timeoutMillis; + return this; + } + + private static String getUserAgent() { + return DEFAULT_USER_AGENT; + } + + private static String httpClientVersion() { + return Optional.ofNullable(BaseHttpRequestBuilder.class.getPackage().getImplementationVersion()) + .orElse("unknown"); + } + + private static String javaVendor() { + return Optional.ofNullable(System.getProperty("java.vendor")) + .orElse("unknown"); + } + + private static String javaVersion() { + return Optional.ofNullable(System.getProperty("java.version")) + .orElse("unknown"); + } } diff --git a/net-http-client/src/main/java/org/xbib/net/http/client/HttpRequest.java b/net-http-client/src/main/java/org/xbib/net/http/client/HttpRequest.java index 4a651cf..24c5287 100644 --- a/net-http-client/src/main/java/org/xbib/net/http/client/HttpRequest.java +++ b/net-http-client/src/main/java/org/xbib/net/http/client/HttpRequest.java @@ -3,12 +3,15 @@ package org.xbib.net.http.client; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; +import java.util.Collection; +import java.util.List; import org.xbib.net.ParameterBuilder; import org.xbib.net.Request; import org.xbib.net.URL; import org.xbib.net.http.HttpHeaders; import org.xbib.net.http.HttpMethod; import org.xbib.net.http.HttpVersion; +import org.xbib.net.http.cookie.Cookie; public interface HttpRequest extends Request { @@ -20,10 +23,18 @@ public interface HttpRequest extends Request { HttpHeaders getHeaders(); - ParameterBuilder getParameters(); + ParameterBuilder getParameterBuilder(); ByteBuffer getBody(); CharBuffer getBodyAsChars(Charset charset); + List getMessages(); + + boolean isBackOffEnabled(); + + BackOff getBackOff(); + + Collection cookies(); + } diff --git a/net-http-client/src/main/java/org/xbib/net/http/client/HttpRequestBuilder.java b/net-http-client/src/main/java/org/xbib/net/http/client/HttpRequestBuilder.java index 427b684..53c38fb 100644 --- a/net-http-client/src/main/java/org/xbib/net/http/client/HttpRequestBuilder.java +++ b/net-http-client/src/main/java/org/xbib/net/http/client/HttpRequestBuilder.java @@ -19,7 +19,7 @@ public interface HttpRequestBuilder { HttpRequestBuilder setBody(ByteBuffer byteBuffer); - HttpRequestBuilder addPart(Part part); + HttpRequestBuilder addMessage(Message message); HttpRequest build() throws UnmappableCharacterException, MalformedInputException; } diff --git a/net-http-client/src/main/java/org/xbib/net/http/client/Part.java b/net-http-client/src/main/java/org/xbib/net/http/client/Message.java similarity index 68% rename from net-http-client/src/main/java/org/xbib/net/http/client/Part.java rename to net-http-client/src/main/java/org/xbib/net/http/client/Message.java index 8baa277..e6ef656 100644 --- a/net-http-client/src/main/java/org/xbib/net/http/client/Part.java +++ b/net-http-client/src/main/java/org/xbib/net/http/client/Message.java @@ -2,8 +2,9 @@ package org.xbib.net.http.client; import java.nio.charset.Charset; import java.nio.file.Path; +import static java.nio.charset.StandardCharsets.UTF_8; -public class Part { +public class Message { private final String contentType; @@ -15,11 +16,11 @@ public class Part { private final Charset charset; - public Part(String contentType, - String contentTransferEncoding, - String name, - Path path, - Charset charset) { + public Message(String contentType, + String contentTransferEncoding, + String name, + Path path, + Charset charset) { this.contentType = contentType; this.contentTransferEncoding = contentTransferEncoding; this.name = name; @@ -46,4 +47,9 @@ public class Part { public Charset getCharset() { return charset; } + + @Override + public String toString() { + return "Message[name=" + name + ",path=" + path + "]"; + } } diff --git a/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http1/Https1ChannelInitializer.java b/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http1/Https1ChannelInitializer.java index 37732d7..363afcb 100644 --- a/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http1/Https1ChannelInitializer.java +++ b/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http1/Https1ChannelInitializer.java @@ -47,7 +47,7 @@ public class Https1ChannelInitializer implements HttpChannelInitializer { @Override public void init(Channel channel, NettyHttpServer nettyHttpServer, NettyCustomizer customizer) { - final HttpAddress httpAddress = channel.attr(NettyHttpsServerConfig.ATTRIBUTE_KEY_HTTP_ADDRESS).get(); + final HttpAddress httpAddress = channel.attr(NettyHttpsServerConfig.ATTRIBUTE_HTTP_ADDRESS).get(); final NettyHttpsServerConfig nettyHttpsServerConfig = (NettyHttpsServerConfig) nettyHttpServer.getNettyHttpServerConfig(); final ServerNameIndicationHandler serverNameIndicationHandler = new ServerNameIndicationHandler(nettyHttpsServerConfig, httpAddress, diff --git a/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http1/Https1Handler.java b/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http1/Https1Handler.java index 600fd6e..e468104 100644 --- a/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http1/Https1Handler.java +++ b/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http1/Https1Handler.java @@ -65,7 +65,7 @@ public class Https1Handler extends ChannelDuplexHandler { } protected void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) { - HttpAddress httpAddress = ctx.channel().attr(NettyHttpsServerConfig.ATTRIBUTE_KEY_HTTP_ADDRESS).get(); + HttpAddress httpAddress = ctx.channel().attr(NettyHttpsServerConfig.ATTRIBUTE_HTTP_ADDRESS).get(); try { HttpResponseBuilder serverResponseBuilder = HttpResponse.builder() .setChannelHandlerContext(ctx); diff --git a/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http2/Https2ChannelInitializer.java b/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http2/Https2ChannelInitializer.java index e1618be..c86825e 100644 --- a/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http2/Https2ChannelInitializer.java +++ b/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http2/Https2ChannelInitializer.java @@ -45,7 +45,7 @@ public class Https2ChannelInitializer implements HttpChannelInitializer { @Override public void init(Channel channel, NettyHttpServer nettyHttpServer, NettyCustomizer customizer) { - final HttpAddress httpAddress = channel.attr(NettyHttpsServerConfig.ATTRIBUTE_KEY_HTTP_ADDRESS).get(); + final HttpAddress httpAddress = channel.attr(NettyHttpsServerConfig.ATTRIBUTE_HTTP_ADDRESS).get(); final NettyHttpsServerConfig nettyHttpsServerConfig = (NettyHttpsServerConfig) nettyHttpServer.getNettyHttpServerConfig(); final ServerNameIndicationHandler serverNameIndicationHandler = new ServerNameIndicationHandler(nettyHttpsServerConfig, httpAddress, diff --git a/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http2/Https2ChildChannelInitializer.java b/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http2/Https2ChildChannelInitializer.java index b62a325..c768b3d 100644 --- a/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http2/Https2ChildChannelInitializer.java +++ b/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http2/Https2ChildChannelInitializer.java @@ -33,7 +33,7 @@ public class Https2ChildChannelInitializer extends ChannelInitializer { @Override protected void initChannel(Channel channel) { NettyHttpsServerConfig nettyHttpsServerConfig = (NettyHttpsServerConfig) server.getNettyHttpServerConfig(); - channel.attr(NettyHttpsServerConfig.ATTRIBUTE_KEY_HTTP_ADDRESS).set(httpAddress); + channel.attr(NettyHttpsServerConfig.ATTRIBUTE_HTTP_ADDRESS).set(httpAddress); channel.attr(NettyHttpsServerConfig.ATTRIBUTE_KEY_SNI_HANDLER).set(serverNameIndicationHandler); ChannelPipeline pipeline = channel.pipeline(); pipeline.addLast("server-frame-converter", new Http2StreamFrameToHttpObjectCodec(true)); diff --git a/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http2/Https2Handler.java b/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http2/Https2Handler.java index 380d059..0604a34 100644 --- a/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http2/Https2Handler.java +++ b/net-http-server-netty-secure/src/main/java/org/xbib/net/http/server/netty/secure/http2/Https2Handler.java @@ -37,7 +37,7 @@ public class Https2Handler extends ChannelDuplexHandler { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof FullHttpRequest fullHttpRequest) { - HttpAddress httpAddress = ctx.channel().attr(NettyHttpsServerConfig.ATTRIBUTE_KEY_HTTP_ADDRESS).get(); + HttpAddress httpAddress = ctx.channel().attr(NettyHttpsServerConfig.ATTRIBUTE_HTTP_ADDRESS).get(); try { Integer streamId = fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text()); HttpResponseBuilder httpsResponseBuilder = HttpResponse.builder() diff --git a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpRequest.java b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpRequest.java index 77293de..54eab1a 100644 --- a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpRequest.java +++ b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpRequest.java @@ -23,12 +23,6 @@ public class HttpRequest extends BaseHttpRequest { return new HttpRequestBuilder(); } - @Override - public InputStream getInputStream() { - //return new ByteBufInputStream(builder.fullHttpRequest.content()); - return builder.byteBuffer != null ? new ByteBufferInputStream(builder.byteBuffer) : null; - } - @Override public ByteBuffer getBody() { return builder.getBody(); @@ -39,6 +33,11 @@ public class HttpRequest extends BaseHttpRequest { return builder.getBodyAsChars(charset); } + @Override + public InputStream getInputStream() { + return builder.byteBuffer != null ? new ByteBufferInputStream(builder.byteBuffer) : null; + } + @Override public R as(Class type) { Objects.requireNonNull(type); diff --git a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpRequestBuilder.java b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpRequestBuilder.java index 67f5d4a..4e9d539 100644 --- a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpRequestBuilder.java +++ b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpRequestBuilder.java @@ -17,12 +17,14 @@ import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; -import org.xbib.net.http.server.Part; +import org.xbib.net.http.server.Message; public class HttpRequestBuilder extends BaseHttpRequestBuilder { private static final Logger logger = Logger.getLogger(HttpRequestBuilder.class.getName()); + private FullHttpRequest httpRequest; + protected ByteBuffer byteBuffer; protected HttpRequestBuilder() { @@ -39,13 +41,16 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder { } public HttpRequestBuilder setFullHttpRequest(FullHttpRequest fullHttpRequest) { - setVersion(HttpVersion.valueOf(fullHttpRequest.protocolVersion().text())); - setMethod(HttpMethod.valueOf(fullHttpRequest.method().name())); - setRequestURI(fullHttpRequest.uri()); - fullHttpRequest.headers().entries().forEach(e -> addHeader(e.getKey(), e.getValue())); - // read all bytes from request into a JDK ByteBuffer. This might be expensive. - if (fullHttpRequest.content() != null) { - byteBuffer = ByteBuffer.wrap(ByteBufUtil.getBytes(fullHttpRequest.content())); + if (fullHttpRequest != null) { + this.httpRequest = fullHttpRequest; + setVersion(HttpVersion.valueOf(fullHttpRequest.protocolVersion().text())); + setMethod(HttpMethod.valueOf(fullHttpRequest.method().name())); + setRequestURI(fullHttpRequest.uri()); + fullHttpRequest.headers().entries().forEach(e -> addHeader(e.getKey(), e.getValue())); + // read all bytes from request into a JDK ByteBuffer. This might be expensive. + if (fullHttpRequest.content() != null) { + byteBuffer = ByteBuffer.wrap(ByteBufUtil.getBytes(fullHttpRequest.content())); + } } return this; } @@ -110,12 +115,14 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder { public HttpRequestBuilder addFileUpload(FileUpload fileUpload) throws IOException { logger.log(Level.FINE, "add file upload = " + fileUpload); - Part part = new Part(fileUpload.getContentType(), + Message message = new Message(fileUpload.getContentType(), fileUpload.getContentTransferEncoding(), fileUpload.getFilename(), fileUpload.isInMemory() ? null : fileUpload.getFile().toPath(), + // can be expensive ByteBuffer.wrap(fileUpload.get())); - super.parts.add(part); + super.messages.add(message); + // we do not need to fileUpload.release() because we let clean up the factory object at the end of channel handling return this; } @@ -130,6 +137,11 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder { @Override public void release() { - + super.release(); + if (httpRequest != null) { + if (httpRequest.refCnt() > 0) { + httpRequest.release(); + } + } } } diff --git a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpResponseBuilder.java b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpResponseBuilder.java index 4ced16d..6e68383 100644 --- a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpResponseBuilder.java +++ b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/HttpResponseBuilder.java @@ -193,7 +193,7 @@ public class HttpResponseBuilder extends BaseHttpResponseBuilder { super.trailingHeaders.entries().forEach(e -> trailingHeaders.add(e.getKey(), e.getValue())); HttpVersion httpVersion = HttpVersion.valueOf(version.text()); FullHttpResponse fullHttpResponse = - new DefaultFullHttpResponse(httpVersion, responseStatus, byteBuf.retain(), headers, trailingHeaders); + new DefaultFullHttpResponse(httpVersion, responseStatus, byteBuf, headers, trailingHeaders); ChannelFuture channelFuture; if (sequenceId != null) { HttpPipelinedResponse httpPipelinedResponse = new HttpPipelinedResponse(fullHttpResponse, diff --git a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/NettyHttpServer.java b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/NettyHttpServer.java index de9590e..2bd9404 100644 --- a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/NettyHttpServer.java +++ b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/NettyHttpServer.java @@ -8,9 +8,9 @@ import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.socket.ServerSocketChannel; +import io.netty.handler.codec.http.multipart.HttpDataFactory; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; -import io.netty.util.AttributeKey; import java.io.IOException; import java.net.BindException; import java.net.InetSocketAddress; @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.ServiceLoader; import java.util.Set; +import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -31,7 +32,6 @@ import org.xbib.net.http.server.HttpResponseBuilder; import org.xbib.net.http.server.HttpServerContext; import org.xbib.net.http.server.HttpServer; import org.xbib.net.http.server.domain.HttpDomain; -import org.xbib.net.http.server.executor.CallableReleasable; import org.xbib.net.http.server.route.HttpRouter; /** @@ -94,27 +94,44 @@ public class NettyHttpServer implements HttpServer { for (HttpAddress httpAddress : httpAddressSet) { SocketConfig socketConfig = httpAddress.getSocketConfig(); ServerBootstrap bootstrap = new ServerBootstrap() - .group(parentEventLoopGroup, childEventLoopGroup) - .channel(socketChannelClass) - .option(ChannelOption.ALLOCATOR, builder.byteBufAllocator) - .option(ChannelOption.SO_REUSEADDR, socketConfig.isReuseAddr()) - .option(ChannelOption.SO_RCVBUF, socketConfig.getTcpReceiveBufferSize()) - .option(ChannelOption.SO_BACKLOG, socketConfig.getBackLogSize()) - .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, socketConfig.getConnectTimeoutMillis()) - .childOption(ChannelOption.ALLOCATOR, builder.byteBufAllocator) - .childOption(ChannelOption.SO_REUSEADDR, socketConfig.isReuseAddr()) - .childOption(ChannelOption.TCP_NODELAY, socketConfig.isTcpNodelay()) - .childOption(ChannelOption.SO_SNDBUF, socketConfig.getTcpSendBufferSize()) - .childOption(ChannelOption.SO_RCVBUF, socketConfig.getTcpReceiveBufferSize()) - .childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, socketConfig.getConnectTimeoutMillis()) - .childHandler(new ChannelInitializer<>() { - @Override - protected void initChannel(Channel ch) { - AttributeKey key = AttributeKey.valueOf("_address"); - ch.attr(key).set(httpAddress); - createChannelInitializer(httpAddress).init(ch, getServer(), builder.nettyCustomizer); - } - }); + .group(parentEventLoopGroup, childEventLoopGroup) + .channel(socketChannelClass) + .option(ChannelOption.ALLOCATOR, builder.byteBufAllocator) + .option(ChannelOption.SO_REUSEADDR, socketConfig.isReuseAddr()) + .option(ChannelOption.SO_RCVBUF, socketConfig.getTcpReceiveBufferSize()) + .option(ChannelOption.SO_BACKLOG, socketConfig.getBackLogSize()) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, socketConfig.getConnectTimeoutMillis()) + .childOption(ChannelOption.ALLOCATOR, builder.byteBufAllocator) + .childOption(ChannelOption.SO_REUSEADDR, socketConfig.isReuseAddr()) + .childOption(ChannelOption.TCP_NODELAY, socketConfig.isTcpNodelay()) + .childOption(ChannelOption.SO_SNDBUF, socketConfig.getTcpSendBufferSize()) + .childOption(ChannelOption.SO_RCVBUF, socketConfig.getTcpReceiveBufferSize()) + .childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, socketConfig.getConnectTimeoutMillis()) + .childHandler(new ChannelInitializer<>() { + @Override + protected void initChannel(Channel channel) { + channel.closeFuture().addListener((ChannelFuture future) -> { + Channel ch = future.channel(); + HttpRequestBuilder httpRequest = ch.attr(NettyHttpServerConfig.ATTRIBUTE_HTTP_REQUEST).get(); + if (httpRequest != null) { + logger.log(Level.FINEST, "releasing HttpRequestBuilder"); + httpRequest.release(); + } + HttpResponseBuilder httpResponse = ch.attr(NettyHttpServerConfig.ATTRIBUTE_HTTP_RESPONSE).get(); + if (httpResponse != null) { + logger.log(Level.FINEST, "releasing HttpResponseBuilder"); + httpResponse.release(); + } + HttpDataFactory httpDataFactory = ch.attr(NettyHttpServerConfig.ATTRIBUTE_HTTP_DATAFACTORY).get(); + if (httpDataFactory != null) { + logger.log(Level.FINEST, "cleaning http data factory"); + httpDataFactory.cleanAllHttpData(); + } + }); + channel.attr(NettyHttpServerConfig.ATTRIBUTE_HTTP_ADDRESS).set(httpAddress); + createChannelInitializer(httpAddress).init(channel, getServer(), builder.nettyCustomizer); + } + }); if (getNettyHttpServerConfig().isDebug()) { bootstrap.handler(new LoggingHandler("server-logging", LogLevel.DEBUG)); } @@ -168,43 +185,25 @@ public class NettyHttpServer implements HttpServer { @Override public void dispatch(HttpRequestBuilder requestBuilder, HttpResponseBuilder responseBuilder) { - CallableReleasable callableReleasable = new CallableReleasable<>() { - @Override - public Object call() { - HttpRouter router = builder.application.getRouter(); - router.route(builder.application, requestBuilder, responseBuilder); - return true; - } - - @Override - public void release() { - requestBuilder.release(); - responseBuilder.release(); - } + Callable callable = (Callable) () -> { + HttpRouter router = builder.application.getRouter(); + router.route(builder.application, requestBuilder, responseBuilder); + return true; }; - builder.application.getExecutor().execute(callableReleasable); + builder.application.getExecutor().execute(callable); } @Override public void dispatch(HttpRequestBuilder requestBuilder, HttpResponseBuilder responseBuilder, HttpResponseStatus responseStatus) { - CallableReleasable callableReleasable = new CallableReleasable<>() { - @Override - public Object call() { - HttpRouter router = builder.application.getRouter(); - HttpServerContext httpServerContext = builder.application.createContext(null, requestBuilder, responseBuilder); - router.routeStatus(responseStatus, httpServerContext); - return true; - } - - @Override - public void release() { - requestBuilder.release(); - responseBuilder.release(); - } + Callable callable = (Callable) () -> { + HttpRouter router = builder.application.getRouter(); + HttpServerContext httpServerContext = builder.application.createContext(null, requestBuilder, responseBuilder); + router.routeStatus(responseStatus, httpServerContext); + return true; }; - builder.application.getExecutor().execute(callableReleasable); + builder.application.getExecutor().execute(callable); } @Override diff --git a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/NettyHttpServerConfig.java b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/NettyHttpServerConfig.java index 9223398..4df13fa 100644 --- a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/NettyHttpServerConfig.java +++ b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/NettyHttpServerConfig.java @@ -1,12 +1,19 @@ package org.xbib.net.http.server.netty; +import io.netty.handler.codec.http.multipart.HttpDataFactory; import io.netty.util.AttributeKey; import org.xbib.net.http.HttpAddress; import org.xbib.net.http.server.HttpServerConfig; public class NettyHttpServerConfig extends HttpServerConfig { - public static final AttributeKey ATTRIBUTE_KEY_HTTP_ADDRESS = AttributeKey.valueOf("_address"); + public static final AttributeKey ATTRIBUTE_HTTP_ADDRESS = AttributeKey.valueOf("_address"); + + public static final AttributeKey ATTRIBUTE_HTTP_REQUEST = AttributeKey.valueOf("_request"); + + public static final AttributeKey ATTRIBUTE_HTTP_RESPONSE = AttributeKey.valueOf("response"); + + public static final AttributeKey ATTRIBUTE_HTTP_DATAFACTORY = AttributeKey.valueOf("_datafactory"); /** * Enforce the transport class name if many transport providers are given. diff --git a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http1/Http1Handler.java b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http1/Http1Handler.java index afd2281..86df43a 100644 --- a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http1/Http1Handler.java +++ b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http1/Http1Handler.java @@ -74,24 +74,29 @@ class Http1Handler extends ChannelDuplexHandler { protected void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) { - HttpAddress httpAddress = ctx.channel().attr(NettyHttpServerConfig.ATTRIBUTE_KEY_HTTP_ADDRESS).get(); + HttpAddress httpAddress = ctx.channel().attr(NettyHttpServerConfig.ATTRIBUTE_HTTP_ADDRESS).get(); try { - HttpResponseBuilder serverResponseBuilder = HttpResponse.builder() + HttpResponseBuilder httpResponseBuilder = HttpResponse.builder() .setChannelHandlerContext(ctx); if (nettyHttpServer.getNettyHttpServerConfig().isPipeliningEnabled()) { - serverResponseBuilder.setSequenceId(sequenceId); + httpResponseBuilder.setSequenceId(sequenceId); } - serverResponseBuilder.shouldClose("close".equalsIgnoreCase(fullHttpRequest.headers().get(HttpHeaderNames.CONNECTION))); + httpResponseBuilder.shouldClose("close".equalsIgnoreCase(fullHttpRequest.headers().get(HttpHeaderNames.CONNECTION))); + ctx.channel().attr(NettyHttpServerConfig.ATTRIBUTE_HTTP_RESPONSE).set(httpResponseBuilder); + final InetSocketAddress localAddress = (InetSocketAddress) ctx.channel().localAddress(); + final InetSocketAddress remoteAddress = (InetSocketAddress) ctx.channel().remoteAddress(); // the base URL construction may fail with exception. In that case, we return a built-in 400 Bad Request. - HttpRequestBuilder serverRequestBuilder = HttpRequest.builder() + HttpRequestBuilder httpRequestBuilder = HttpRequest.builder() .setFullHttpRequest(fullHttpRequest) .setBaseURL(httpAddress, fullHttpRequest.uri(), fullHttpRequest.headers().get(HttpHeaderNames.HOST)) - .setLocalAddress((InetSocketAddress) ctx.channel().localAddress()) - .setRemoteAddress((InetSocketAddress) ctx.channel().remoteAddress()) + .setLocalAddress(localAddress) + .setRemoteAddress(remoteAddress) .setSequenceId(sequenceId); - nettyHttpServer.dispatch(serverRequestBuilder, serverResponseBuilder); + ctx.channel().attr(NettyHttpServerConfig.ATTRIBUTE_HTTP_REQUEST).set(httpRequestBuilder); + logger.log(Level.FINEST, () -> "incoming connection: " + remoteAddress + " -> " + localAddress); + nettyHttpServer.dispatch(httpRequestBuilder, httpResponseBuilder); } catch (Exception e) { logger.log(Level.SEVERE, "bad request: " + e.getMessage(), e); DefaultFullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, diff --git a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http1/HttpFileUploadHandler.java b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http1/HttpFileUploadHandler.java index 004a406..933e367 100644 --- a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http1/HttpFileUploadHandler.java +++ b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http1/HttpFileUploadHandler.java @@ -10,7 +10,6 @@ import io.netty.handler.codec.http.HttpObject; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; -import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory; import io.netty.handler.codec.http.multipart.FileUpload; import io.netty.handler.codec.http.multipart.HttpDataFactory; @@ -45,7 +44,9 @@ public class HttpFileUploadHandler extends SimpleChannelInboundHandler { @Override protected void initChannel(Channel channel) { NettyHttpServerConfig nettyHttpServerConfig = nettyHttpServer.getNettyHttpServerConfig(); - channel.attr(NettyHttpServerConfig.ATTRIBUTE_KEY_HTTP_ADDRESS).set(httpAddress); + channel.attr(NettyHttpServerConfig.ATTRIBUTE_HTTP_ADDRESS).set(httpAddress); ChannelPipeline pipeline = channel.pipeline(); pipeline.addLast("server-frame-converter", new Http2StreamFrameToHttpObjectCodec(true)); diff --git a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http2/Http2Handler.java b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http2/Http2Handler.java index b4957c9..8856340 100644 --- a/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http2/Http2Handler.java +++ b/net-http-server-netty/src/main/java/org/xbib/net/http/server/netty/http2/Http2Handler.java @@ -1,5 +1,6 @@ package org.xbib.net.http.server.netty.http2; +import io.netty.channel.Channel; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandler; @@ -8,6 +9,7 @@ import io.netty.handler.codec.http.DefaultFullHttpResponse; 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.http.multipart.HttpDataFactory; import io.netty.handler.codec.http2.HttpConversionUtil; import java.io.IOException; import java.net.InetSocketAddress; @@ -37,7 +39,7 @@ public class Http2Handler extends ChannelDuplexHandler { @Override public void channelRead(ChannelHandlerContext ctx, Object object) throws IOException { if (object instanceof FullHttpRequest fullHttpRequest) { - HttpAddress httpAddress = ctx.channel().attr(NettyHttpServerConfig.ATTRIBUTE_KEY_HTTP_ADDRESS).get(); + HttpAddress httpAddress = ctx.channel().attr(NettyHttpServerConfig.ATTRIBUTE_HTTP_ADDRESS).get(); try { Integer streamId = fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text()); HttpResponseBuilder httpResponseBuilder = HttpResponse.builder() @@ -47,23 +49,26 @@ public class Http2Handler extends ChannelDuplexHandler { if (streamId != null) { httpResponseBuilder.setStreamId(streamId + 1); } - HttpRequestBuilder serverRequestBuilder = HttpRequest.builder() + ctx.channel().attr(NettyHttpServerConfig.ATTRIBUTE_HTTP_RESPONSE).set(httpResponseBuilder); + final InetSocketAddress localAddress = (InetSocketAddress) ctx.channel().localAddress(); + final InetSocketAddress remoteAddress = (InetSocketAddress) ctx.channel().remoteAddress(); + HttpRequestBuilder httpRequestBuilder = HttpRequest.builder() .setFullHttpRequest(fullHttpRequest) .setBaseURL(httpAddress, fullHttpRequest.uri(), fullHttpRequest.headers().get(HttpHeaderNames.HOST)) - .setLocalAddress((InetSocketAddress) ctx.channel().localAddress()) - .setRemoteAddress((InetSocketAddress) ctx.channel().remoteAddress()) + .setLocalAddress(localAddress) + .setRemoteAddress(remoteAddress) .setStreamId(streamId); - nettyHttpServer.dispatch(serverRequestBuilder, httpResponseBuilder); + ctx.channel().attr(NettyHttpServerConfig.ATTRIBUTE_HTTP_REQUEST).set(httpRequestBuilder); + logger.log(Level.FINEST, () -> "incoming connection: " + remoteAddress + " -> " + localAddress); + nettyHttpServer.dispatch(httpRequestBuilder, httpResponseBuilder); } catch (Exception e) { logger.log(Level.SEVERE, "bad request:" + e.getMessage(), e); DefaultFullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(io.netty.handler.codec.http.HttpVersion.valueOf(httpAddress.getVersion().text()), HttpResponseStatus.BAD_REQUEST); ctx.writeAndFlush(fullHttpResponse).addListener(ChannelFutureListener.CLOSE); - } finally { - fullHttpRequest.release(); } } } @@ -78,4 +83,24 @@ public class Http2Handler extends ChannelDuplexHandler { logger.log(Level.SEVERE, cause.getMessage(), cause); ctx.close(); } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + Channel ch = ctx.channel(); + HttpRequestBuilder httpRequest = ch.attr(NettyHttpServerConfig.ATTRIBUTE_HTTP_REQUEST).get(); + if (httpRequest != null) { + logger.log(Level.FINEST, "releasing HttpRequestBuilder"); + httpRequest.release(); + } + HttpResponseBuilder httpResponse = ch.attr(NettyHttpServerConfig.ATTRIBUTE_HTTP_RESPONSE).get(); + if (httpResponse != null) { + logger.log(Level.FINEST, "releasing HttpResponseBuilder"); + httpResponse.release(); + } + HttpDataFactory httpDataFactory = ch.attr(NettyHttpServerConfig.ATTRIBUTE_HTTP_DATAFACTORY).get(); + if (httpDataFactory != null) { + logger.log(Level.FINEST, "cleaning http data factory"); + httpDataFactory.cleanAllHttpData(); + } + } } diff --git a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttp2ServerMultiRequestLoadTest.java b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttp2ServerMultiRequestLoadTest.java index e764a79..4ccf950 100644 --- a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttp2ServerMultiRequestLoadTest.java +++ b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttp2ServerMultiRequestLoadTest.java @@ -33,8 +33,11 @@ public class NettyHttp2ServerMultiRequestLoadTest { private static final Logger logger = Logger.getLogger(NettyHttp2ServerMultiRequestLoadTest.class.getName()); @Test - public void testHttp2Load() throws Exception { + public void testHttp2Multi() throws Exception { + + int requests = 1024; ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.PARANOID); + URL url = URL.from("http://localhost:8008/domain"); HttpAddress httpAddress = HttpAddress.http2(url); NettyHttpServerConfig serverConfig = new NettyHttpServerConfig(); @@ -74,7 +77,6 @@ public class NettyHttp2ServerMultiRequestLoadTest { .build()) { server.bind(); NettyHttpClientConfig config = new NettyHttpClientConfig(); - int requests = 1024; AtomicInteger count = new AtomicInteger(); try (NettyHttpClient client = NettyHttpClient.builder() .setConfig(config) diff --git a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttp2ServerTest.java b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttp2ServerTest.java index 13da14a..d12c990 100644 --- a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttp2ServerTest.java +++ b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttp2ServerTest.java @@ -39,7 +39,7 @@ public class NettyHttp2ServerTest { NettyHttpServerConfig nettyHttpServerConfig = new NettyHttpServerConfig(); nettyHttpServerConfig.setServerName("NettyHttp2ClearTextServer", Bootstrap.class.getPackage().getImplementationVersion()); - nettyHttpServerConfig.setNetworkClass(NetworkClass.ANY); + nettyHttpServerConfig.setNetworkClass(NetworkClass.LOCAL); nettyHttpServerConfig.setDebug(true); HttpRouter router = BaseHttpRouter.builder() @@ -52,10 +52,11 @@ public class NettyHttp2ServerTest { .setResponseStatus(HttpResponseStatus.OK) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setCharset(StandardCharsets.UTF_8); - ctx.write("domain " + + ctx.write("Hello, here is my response: " + ctx.httpRequest().getParameter() + " " + ctx.httpRequest().getLocalAddress() + " " + ctx.httpRequest().getRemoteAddress()); + ctx.done(); }) .build()) .build()) diff --git a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerBodyTest.java b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerBodyTest.java index e347a1d..5ce4be2 100644 --- a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerBodyTest.java +++ b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerBodyTest.java @@ -55,8 +55,8 @@ public class NettyHttpServerBodyTest { " local address = " + ctx.httpRequest().getLocalAddress() + " remote address = " + ctx.httpRequest().getRemoteAddress() + " attributes = " + ctx.getAttributes() + - " body = " + body - ); + " body = " + body); + ctx.done(); }) .build()) .build()) diff --git a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerFileUploadTest.java b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerFileUploadTest.java index 9b948f1..807eed0 100644 --- a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerFileUploadTest.java +++ b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerFileUploadTest.java @@ -1,12 +1,17 @@ package org.xbib.net.http.netty.test; import io.netty.bootstrap.Bootstrap; +import java.io.InputStream; +import java.io.OutputStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.xbib.net.NetworkClass; import org.xbib.net.URL; @@ -15,7 +20,7 @@ import org.xbib.net.http.HttpHeaderNames; import org.xbib.net.http.HttpHeaderValues; import org.xbib.net.http.HttpMethod; import org.xbib.net.http.HttpResponseStatus; -import org.xbib.net.http.client.Part; +import org.xbib.net.http.client.Message; import org.xbib.net.http.client.netty.HttpRequest; import org.xbib.net.http.client.netty.NettyHttpClient; import org.xbib.net.http.client.netty.NettyHttpClientConfig; @@ -35,8 +40,8 @@ public class NettyHttpServerFileUploadTest { private static final Logger logger = Logger.getLogger(NettyHttpServerFileUploadTest.class.getName()); @Test - public void testFileUpload() throws Exception { - URL url = URL.from("http://localhost:8008/domain/"); + public void testSimpleFileUpload() throws Exception { + URL url = URL.from("http://localhost:8008/"); HttpAddress httpAddress1 = HttpAddress.http1(url); NettyHttpServerConfig nettyHttpServerConfig = new NettyHttpServerConfig(); nettyHttpServerConfig.setServerName("NettyHttpServer", @@ -50,11 +55,14 @@ public class NettyHttpServerFileUploadTest { .addDomain(BaseHttpDomain.builder() .setHttpAddress(httpAddress1) .addService(BaseHttpService.builder() - .setPath("/domain") + .setPath("/") .setMethod(HttpMethod.POST) .setHandler(ctx -> { logger.log(Level.FINEST, "handler starting"); - List parts = ctx.httpRequest().getParts(); + String message = ctx.httpRequest().getMessages().stream() + .map(m -> StandardCharsets.UTF_8.decode(m.getByteBuffer())) + .collect(Collectors.joining()); + ctx.response() .setResponseStatus(HttpResponseStatus.OK) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) @@ -63,7 +71,7 @@ public class NettyHttpServerFileUploadTest { " local address = " + ctx.httpRequest().getLocalAddress() + " remote address = " + ctx.httpRequest().getRemoteAddress() + " attributes = " + ctx.getAttributes() + - " parts = " + parts + " message = " + message ); }) .build()) @@ -82,9 +90,10 @@ public class NettyHttpServerFileUploadTest { .build()) { server.bind(); NettyHttpClientConfig config = new NettyHttpClientConfig() - .setGzipEnabled(false) + .setGzipEnabled(true) .setChunkWriteEnabled(true) .setObjectAggregationEnabled(true) + .setFileUploadEnabled(true) .setDebug(true); AtomicBoolean received = new AtomicBoolean(); try (NettyHttpClient client = NettyHttpClient.builder() @@ -92,8 +101,96 @@ public class NettyHttpServerFileUploadTest { .build()) { HttpRequest request = HttpRequest.post() .setURL(url) - .addPart(new Part("text/plain", "base64", - "test", Paths.get("build.gradle"), StandardCharsets.UTF_8)) + .addMessage(new Message("text/plain", + "base64", + "test", Paths.get("build.gradle"), + StandardCharsets.UTF_8)) + .setResponseListener(resp -> { + logger.log(Level.INFO, "got response:" + + " status = " + resp.getStatus() + + " header = " + resp.getHeaders() + + " body = " + resp.getBodyAsChars(StandardCharsets.UTF_8)); + received.set(true); + }) + .build(); + client.execute(request).get().close(); + } + assertTrue(received.get()); + } + } + + @Disabled + @Test + public void testLargeFileUpload() throws Exception { + URL url = URL.from("http://localhost:8008/"); + HttpAddress httpAddress1 = HttpAddress.http1(url); + NettyHttpServerConfig nettyHttpServerConfig = new NettyHttpServerConfig(); + nettyHttpServerConfig.setServerName("NettyHttpServer", + Bootstrap.class.getPackage().getImplementationVersion()); + nettyHttpServerConfig.setNetworkClass(NetworkClass.LOCAL); + nettyHttpServerConfig.setDebug(false); + nettyHttpServerConfig.setChunkWriteEnabled(true); + nettyHttpServerConfig.setFileUploadEnabled(true); + + HttpRouter router = BaseHttpRouter.builder() + .addDomain(BaseHttpDomain.builder() + .setHttpAddress(httpAddress1) + .addService(BaseHttpService.builder() + .setPath("/") + .setMethod(HttpMethod.POST) + .setHandler(ctx -> { + List messages = ctx.httpRequest().getMessages(); + for (org.xbib.net.http.server.Message message : messages) { + if (message.getPath() != null) { + try (InputStream inputStream = Files.newInputStream(message.getPath()); + OutputStream outputStream = Files.newOutputStream(Paths.get("build/" + message.getName()))) { + inputStream.transferTo(outputStream); + } + } + } + ctx.response() + .setResponseStatus(HttpResponseStatus.OK) + .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) + .setCharset(StandardCharsets.UTF_8); + ctx.write("parameter = " + ctx.httpRequest().getParameter().allToString() + + " local address = " + ctx.httpRequest().getLocalAddress() + + " remote address = " + ctx.httpRequest().getRemoteAddress() + + " attributes = " + ctx.getAttributes() + + " parts = " + messages.size() + ); + }) + .build()) + .build()) + .build(); + + Executor executor = BaseExecutor.builder() + .build(); + + try (NettyHttpServer server = NettyHttpServer.builder() + .setHttpServerConfig(nettyHttpServerConfig) + .setApplication(BaseApplication.builder() + .setExecutor(executor) + .setRouter(router) + .build()) + .build()) { + server.bind(); + NettyHttpClientConfig config = new NettyHttpClientConfig() + .setGzipEnabled(true) + .setChunkWriteEnabled(true) + .setObjectAggregationEnabled(true) + .setFileUploadEnabled(true) + .setDebug(false); + AtomicBoolean received = new AtomicBoolean(); + try (NettyHttpClient client = NettyHttpClient.builder() + .setConfig(config) + .build()) { + HttpRequest request = HttpRequest.post() + .setURL(url) + .addMessage(new Message("application/pdf", + "base64", + "test.pdf", + Paths.get("/home/joerg/3904846.pdf"), + StandardCharsets.US_ASCII)) .setResponseListener(resp -> { logger.log(Level.INFO, "got response:" + " status = " + resp.getStatus() + diff --git a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerMultiRequestLoadTest.java b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerMultiRequestLoadTest.java index 01b5524..7a72a76 100644 --- a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerMultiRequestLoadTest.java +++ b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerMultiRequestLoadTest.java @@ -33,13 +33,16 @@ public class NettyHttpServerMultiRequestLoadTest { private static final Logger logger = Logger.getLogger(NettyHttpServerMultiRequestLoadTest.class.getName()); @Test - public void loadTestHttp1() throws Exception { + public void testHttp1Multi() throws Exception { + + int requests = 1024; ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.PARANOID); + URL url = URL.from("http://localhost:8008/domain"); HttpAddress httpAddress = HttpAddress.http1(url); NettyHttpServerConfig serverConfig = new NettyHttpServerConfig(); serverConfig.setServerName("NettyHttpServer", Bootstrap.class.getPackage().getImplementationVersion()); - serverConfig.setNetworkClass(NetworkClass.LOOPBACK); + serverConfig.setNetworkClass(NetworkClass.LOCAL); HttpRouter router = BaseHttpRouter.builder() .addDomain(BaseHttpDomain.builder() @@ -57,6 +60,7 @@ public class NettyHttpServerMultiRequestLoadTest { " attributes = " + ctx.getAttributes() + " local address = " + ctx.httpRequest().getLocalAddress() + " remote address = " + ctx.httpRequest().getRemoteAddress()); + ctx.done(); }) .build()) .build()) @@ -74,7 +78,6 @@ public class NettyHttpServerMultiRequestLoadTest { .build()) { server.bind(); NettyHttpClientConfig config = new NettyHttpClientConfig(); - int requests = 1024; AtomicInteger count = new AtomicInteger(); try (NettyHttpClient client = NettyHttpClient.builder() .setConfig(config) diff --git a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerTest.java b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerTest.java index 236a9d9..8346612 100644 --- a/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerTest.java +++ b/net-http-server-netty/src/test/java/org/xbib/net/http/netty/test/NettyHttpServerTest.java @@ -40,7 +40,6 @@ public class NettyHttpServerTest { Bootstrap.class.getPackage().getImplementationVersion()); nettyHttpServerConfig.setNetworkClass(NetworkClass.LOCAL); nettyHttpServerConfig.setDebug(true); - nettyHttpServerConfig.setPipelining(false); HttpRouter router = BaseHttpRouter.builder() .addDomain(BaseHttpDomain.builder() @@ -56,8 +55,8 @@ public class NettyHttpServerTest { " parameter = " + ctx.httpRequest().getParameter().allToString() + " local address = " + ctx.httpRequest().getLocalAddress() + " remote address = " + ctx.httpRequest().getRemoteAddress() + - " attributes = " + ctx.getAttributes() - ); + " attributes = " + ctx.getAttributes()); + ctx.done(); }) .build()) .build()) diff --git a/net-http-server-nio/src/main/java/org/xbib/net/http/server/nio/HttpResponse.java b/net-http-server-nio/src/main/java/org/xbib/net/http/server/nio/HttpResponse.java index c447b2d..c9df288 100644 --- a/net-http-server-nio/src/main/java/org/xbib/net/http/server/nio/HttpResponse.java +++ b/net-http-server-nio/src/main/java/org/xbib/net/http/server/nio/HttpResponse.java @@ -1,17 +1,8 @@ package org.xbib.net.http.server.nio; -import org.xbib.net.buffer.DataBuffer; import org.xbib.net.http.server.BaseHttpResponse; import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.nio.channels.WritableByteChannel; -import java.util.Objects; - -import static java.nio.charset.StandardCharsets.US_ASCII; public class HttpResponse extends BaseHttpResponse { @@ -30,5 +21,4 @@ public class HttpResponse extends BaseHttpResponse { public static HttpResponseBuilder builder() { return new HttpResponseBuilder(); } - } diff --git a/net-http-server-nio/src/main/java/org/xbib/net/http/server/nio/HttpResponseBuilder.java b/net-http-server-nio/src/main/java/org/xbib/net/http/server/nio/HttpResponseBuilder.java index f54d6a7..f944ebf 100644 --- a/net-http-server-nio/src/main/java/org/xbib/net/http/server/nio/HttpResponseBuilder.java +++ b/net-http-server-nio/src/main/java/org/xbib/net/http/server/nio/HttpResponseBuilder.java @@ -48,6 +48,8 @@ public class HttpResponseBuilder extends BaseHttpResponseBuilder { internalWrite(fileChannel, bufferSize); } else if (inputStream != null) { internalWrite(inputStream, bufferSize); + } else { + internalFlush(); } } catch (IOException e) { logger.log(Level.SEVERE, e.getMessage(), e); diff --git a/net-http-server-nio/src/main/java/org/xbib/net/http/server/nio/NioHttpServer.java b/net-http-server-nio/src/main/java/org/xbib/net/http/server/nio/NioHttpServer.java index 1640d23..4b05994 100644 --- a/net-http-server-nio/src/main/java/org/xbib/net/http/server/nio/NioHttpServer.java +++ b/net-http-server-nio/src/main/java/org/xbib/net/http/server/nio/NioHttpServer.java @@ -1,6 +1,7 @@ package org.xbib.net.http.server.nio; import java.util.Collection; +import java.util.concurrent.Callable; import org.xbib.net.http.HttpAddress; import org.xbib.net.http.HttpHeaderNames; import org.xbib.net.http.HttpHeaders; @@ -33,7 +34,6 @@ import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.xbib.net.http.server.domain.HttpDomain; -import org.xbib.net.http.server.executor.CallableReleasable; import org.xbib.net.http.server.route.HttpRouter; public class NioHttpServer implements HttpServer { @@ -135,21 +135,12 @@ public class NioHttpServer implements HttpServer { @Override public void dispatch(org.xbib.net.http.server.HttpRequestBuilder requestBuilder, org.xbib.net.http.server.HttpResponseBuilder responseBuilder) { - CallableReleasable callableReleasable = new CallableReleasable<>() { - @Override - public Object call() { - HttpRouter router = builder.application.getRouter(); - router.route(builder.application, requestBuilder, responseBuilder); - return true; - } - - @Override - public void release() { - requestBuilder.release(); - responseBuilder.release(); - } + Callable callable = (Callable) () -> { + HttpRouter router = builder.application.getRouter(); + router.route(builder.application, requestBuilder, responseBuilder); + return true; }; - builder.application.getExecutor().execute(callableReleasable); + builder.application.getExecutor().execute(callable); } @Override @@ -157,21 +148,12 @@ public class NioHttpServer implements HttpServer { org.xbib.net.http.server.HttpResponseBuilder responseBuilder, HttpResponseStatus responseStatus) { HttpServerContext httpServerContext = builder.application.createContext(null, requestBuilder, responseBuilder); - CallableReleasable callableReleasable = new CallableReleasable<>() { - @Override - public Object call() { - HttpRouter router = builder.application.getRouter(); - router.routeStatus(responseStatus, httpServerContext); - return true; - } - - @Override - public void release() { - requestBuilder.release(); - responseBuilder.release(); - } + Callable callable = (Callable) () -> { + HttpRouter router = builder.application.getRouter(); + router.routeStatus(responseStatus, httpServerContext); + return true; }; - builder.application.getExecutor().execute(callableReleasable); + builder.application.getExecutor().execute(callable); } @Override diff --git a/net-http-server-simple/src/main/java/org/xbib/net/http/server/simple/SimpleHttpServer.java b/net-http-server-simple/src/main/java/org/xbib/net/http/server/simple/SimpleHttpServer.java index 44cf418..5ff7d75 100644 --- a/net-http-server-simple/src/main/java/org/xbib/net/http/server/simple/SimpleHttpServer.java +++ b/net-http-server-simple/src/main/java/org/xbib/net/http/server/simple/SimpleHttpServer.java @@ -1,6 +1,7 @@ package org.xbib.net.http.server.simple; import java.util.Collection; +import java.util.concurrent.Callable; import org.xbib.net.NetworkClass; import org.xbib.net.NetworkUtils; import org.xbib.net.SocketConfig; @@ -34,7 +35,6 @@ import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.xbib.net.http.server.domain.HttpDomain; -import org.xbib.net.http.server.executor.CallableReleasable; import org.xbib.net.http.server.route.HttpRouter; public class SimpleHttpServer implements HttpServer { @@ -137,19 +137,10 @@ public class SimpleHttpServer implements HttpServer { @Override public void dispatch(org.xbib.net.http.server.HttpRequestBuilder requestBuilder, org.xbib.net.http.server.HttpResponseBuilder responseBuilder) { - CallableReleasable callableReleasable = new CallableReleasable<>() { - @Override - public Object call() { - HttpRouter router = builder.application.getRouter(); - router.route(builder.application, requestBuilder, responseBuilder); - return true; - } - - @Override - public void release() { - requestBuilder.release(); - responseBuilder.release(); - } + Callable callableReleasable = (Callable) () -> { + HttpRouter router = builder.application.getRouter(); + router.route(builder.application, requestBuilder, responseBuilder); + return true; }; builder.application.getExecutor().execute(callableReleasable); } @@ -159,21 +150,12 @@ public class SimpleHttpServer implements HttpServer { org.xbib.net.http.server.HttpResponseBuilder responseBuilder, HttpResponseStatus responseStatus) { HttpServerContext httpServerContext = builder.application.createContext(null, requestBuilder, responseBuilder); - CallableReleasable callableReleasable = new CallableReleasable<>() { - @Override - public Object call() { - HttpRouter router = builder.application.getRouter(); - router.routeStatus(responseStatus, httpServerContext); - return true; - } - - @Override - public void release() { - requestBuilder.release(); - responseBuilder.release(); - } + Callable callable = (Callable) () -> { + HttpRouter router = builder.application.getRouter(); + router.routeStatus(responseStatus, httpServerContext); + return true; }; - builder.application.getExecutor().execute(callableReleasable); + builder.application.getExecutor().execute(callable); } @Override diff --git a/net-http-server-simple/src/test/java/org/xbib/net/http/server/simple/test/HttpRouterTest.java b/net-http-server-simple/src/test/java/org/xbib/net/http/server/simple/test/HttpRouterTest.java index c781930..cae411e 100644 --- a/net-http-server-simple/src/test/java/org/xbib/net/http/server/simple/test/HttpRouterTest.java +++ b/net-http-server-simple/src/test/java/org/xbib/net/http/server/simple/test/HttpRouterTest.java @@ -8,6 +8,7 @@ import org.xbib.net.http.HttpHeaderValues; import org.xbib.net.http.HttpMethod; import org.xbib.net.http.HttpResponseStatus; import org.xbib.net.http.HttpVersion; +import org.xbib.net.http.server.application.Application; import org.xbib.net.http.server.application.BaseApplication; import org.xbib.net.http.server.domain.BaseHttpDomain; import org.xbib.net.http.server.route.BaseHttpRouter; @@ -30,6 +31,7 @@ public class HttpRouterTest { public void routerTest() throws Exception { URL baseURL = URL.http().host("localhost").port(8008).build(); HttpAddress httpAddress = HttpAddress.of(baseURL); + BaseHttpRouter router = BaseHttpRouter.builder() .addDomain(BaseHttpDomain.builder() .setHttpAddress(httpAddress) @@ -47,16 +49,25 @@ public class HttpRouterTest { .build()) .build()) .build(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + HttpResponseBuilder httpResponse = HttpResponse.builder() .setOutputStream(outputStream); + HttpRequestBuilder httpRequest = HttpRequest.builder() .setBaseURL(baseURL) .setVersion(HttpVersion.HTTP_1_1) .setMethod(HttpMethod.DELETE) .setRequestURI("/demo") .addHeader(HttpHeaderNames.HOST, httpAddress.hostAndPort()); - router.route(BaseApplication.builder().build(), httpRequest, httpResponse); + + Application application = BaseApplication.builder() + .setRouter(router) + .build(); + + router.route(application, httpRequest, httpResponse); + String string = outputStream.toString(StandardCharsets.UTF_8); Logger.getAnonymousLogger().log(Level.INFO, "the response string is = " + string); assertTrue(string.contains("/demo")); diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpRequest.java b/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpRequest.java index 91517e0..5785f3e 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpRequest.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpRequest.java @@ -87,8 +87,8 @@ public abstract class BaseHttpRequest implements HttpRequest { } @Override - public List getParts() { - return builder.parts; + public List getMessages() { + return builder.messages; } @Override diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpRequestBuilder.java b/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpRequestBuilder.java index d8678af..d133ac1 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpRequestBuilder.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpRequestBuilder.java @@ -51,11 +51,11 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder { protected boolean done; - protected List parts; + protected List messages; protected BaseHttpRequestBuilder() { this.httpHeaders = new HttpHeaders(); - this.parts = new ArrayList<>(); + this.messages = new ArrayList<>(); } @Override @@ -269,11 +269,11 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder { return this; } - public BaseHttpRequestBuilder addPart(Part part) { + public BaseHttpRequestBuilder addPart(Message message) { if (done) { return this; } - this.parts.add(part); + this.messages.add(message); return this; } @@ -282,6 +282,11 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder { this.done = true; } + @Override + public void release() { + // do nothing for now + } + private static String stripPort(String hostMaybePort) { if (hostMaybePort == null) { return null; @@ -297,5 +302,4 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder { int i = hostMaybePort.lastIndexOf(':'); return i >= 0 ? hostMaybePort.substring(i + 1) : null; } - } diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpResponse.java b/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpResponse.java index 49bd46f..26c07c8 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpResponse.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/BaseHttpResponse.java @@ -7,4 +7,9 @@ public abstract class BaseHttpResponse implements HttpResponse { protected BaseHttpResponse(BaseHttpResponseBuilder builder) { this.builder = builder; } + + @Override + public void release() { + builder.release(); + } } diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/HttpRequest.java b/net-http-server/src/main/java/org/xbib/net/http/server/HttpRequest.java index c16ac5e..d6b2e40 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/HttpRequest.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/HttpRequest.java @@ -2,7 +2,6 @@ package org.xbib.net.http.server; import java.io.InputStream; import java.nio.ByteBuffer; -import java.nio.file.Path; import java.util.List; import org.xbib.net.Attributes; import org.xbib.net.Parameter; @@ -40,7 +39,7 @@ public interface HttpRequest extends Request { InputStream getInputStream(); - List getParts(); + List getMessages(); Attributes getAttributes(); } diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/HttpRequestBuilder.java b/net-http-server/src/main/java/org/xbib/net/http/server/HttpRequestBuilder.java index 00fdcfe..ee7ead7 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/HttpRequestBuilder.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/HttpRequestBuilder.java @@ -31,7 +31,7 @@ public interface HttpRequestBuilder { HttpRequestBuilder addHeader(String name, String value); - HttpRequestBuilder addPart(Part part); + HttpRequestBuilder addPart(Message message); URL getBaseURL(); diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/HttpResponse.java b/net-http-server/src/main/java/org/xbib/net/http/server/HttpResponse.java index 6625d98..a824be8 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/HttpResponse.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/HttpResponse.java @@ -4,4 +4,5 @@ import org.xbib.net.Response; public interface HttpResponse extends Response { + void release(); } diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/Part.java b/net-http-server/src/main/java/org/xbib/net/http/server/Message.java similarity index 69% rename from net-http-server/src/main/java/org/xbib/net/http/server/Part.java rename to net-http-server/src/main/java/org/xbib/net/http/server/Message.java index fb6749d..1e86f7b 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/Part.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/Message.java @@ -2,9 +2,8 @@ package org.xbib.net.http.server; import java.nio.ByteBuffer; import java.nio.file.Path; -import static java.nio.charset.StandardCharsets.UTF_8; -public class Part { +public class Message { private final String contentType; @@ -16,11 +15,11 @@ public class Part { private final ByteBuffer byteBuffer; - public Part(String contentType, - String contentTransferEncoding, - String name, - Path path, - ByteBuffer byteBuffer) { + public Message(String contentType, + String contentTransferEncoding, + String name, + Path path, + ByteBuffer byteBuffer) { this.contentType = contentType; this.contentTransferEncoding = contentTransferEncoding; this.name = name; @@ -50,6 +49,6 @@ public class Part { @Override public String toString() { - return "Part[name=" + name + ",path=" + path + ",bytebuffer=" + (byteBuffer != null ? UTF_8.decode(byteBuffer) : "") + "]"; + return "Message[name=" + name + ",path=" + path + "]"; } } diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/application/BaseApplicationBuilder.java b/net-http-server/src/main/java/org/xbib/net/http/server/application/BaseApplicationBuilder.java index 5072c3f..15557c2 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/application/BaseApplicationBuilder.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/application/BaseApplicationBuilder.java @@ -158,7 +158,7 @@ public class BaseApplicationBuilder implements ApplicationBuilder { @Override public Application build() { - Objects.requireNonNull(httpRouter); + Objects.requireNonNull(httpRouter, "http router must not be null"); return new BaseApplication(this); } } diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/executor/BaseExecutor.java b/net-http-server/src/main/java/org/xbib/net/http/server/executor/BaseExecutor.java index 986c844..ce445af 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/executor/BaseExecutor.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/executor/BaseExecutor.java @@ -2,6 +2,7 @@ package org.xbib.net.http.server.executor; import java.io.IOException; import java.util.List; +import java.util.concurrent.Callable; import java.util.logging.Level; import java.util.logging.Logger; @@ -20,8 +21,8 @@ public class BaseExecutor implements Executor { } @Override - public void execute(CallableReleasable callableReleasable) { - builder.executor.submit(callableReleasable); + public void execute(Callable callable) { + builder.executor.submit(callable); } @Override diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/executor/BaseThreadPoolExecutor.java b/net-http-server/src/main/java/org/xbib/net/http/server/executor/BaseThreadPoolExecutor.java index 2b72513..93ae7ab 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/executor/BaseThreadPoolExecutor.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/executor/BaseThreadPoolExecutor.java @@ -45,10 +45,5 @@ public class BaseThreadPoolExecutor extends ThreadPoolExecutor { logger.log(Level.SEVERE, terminationCause.getMessage(), terminationCause); return; } - if (runnable instanceof Task task) { - CallableReleasable callableReleasable = (CallableReleasable) task.getCallable(); - logger.log(Level.FINEST, () -> "releasing " + callableReleasable); - callableReleasable.release(); - } } } diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/executor/CallableReleasable.java b/net-http-server/src/main/java/org/xbib/net/http/server/executor/CallableReleasable.java deleted file mode 100644 index 07cb84e..0000000 --- a/net-http-server/src/main/java/org/xbib/net/http/server/executor/CallableReleasable.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.xbib.net.http.server.executor; - -import java.util.concurrent.Callable; -import org.xbib.net.buffer.Releasable; - -public interface CallableReleasable extends Callable, Releasable { -} diff --git a/net-http-server/src/main/java/org/xbib/net/http/server/executor/Executor.java b/net-http-server/src/main/java/org/xbib/net/http/server/executor/Executor.java index 459ce78..daf226c 100644 --- a/net-http-server/src/main/java/org/xbib/net/http/server/executor/Executor.java +++ b/net-http-server/src/main/java/org/xbib/net/http/server/executor/Executor.java @@ -1,13 +1,11 @@ package org.xbib.net.http.server.executor; import java.io.IOException; +import java.util.concurrent.Callable; public interface Executor { - /** - * Execute a task that must be released after execution. - */ - void execute(CallableReleasable callableReleasable); + void execute(Callable callable); void shutdown() throws IOException; }