implement file uploading, release resource in channel close handler, clean up request/response API

This commit is contained in:
Jörg Prante 2023-04-09 23:01:08 +02:00
parent dab29dbf9f
commit 98b13c8dc7
54 changed files with 993 additions and 1176 deletions

View file

@ -1,321 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC
"-//Puppy Crawl//DTD Check Configuration 1.3//EN"
"http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
<!-- This is a checkstyle configuration file. For descriptions of
what the following rules do, please see the checkstyle configuration
page at http://checkstyle.sourceforge.net/config.html -->
<module name="Checker">
<module name="FileTabCharacter">
<!-- Checks that there are no tab characters in the file.
-->
</module>
<module name="NewlineAtEndOfFile">
<property name="lineSeparator" value="lf"/>
</module>
<module name="RegexpSingleline">
<!-- Checks that FIXME is not used in comments. TODO is preferred.
-->
<property name="format" value="((//.*)|(\*.*))FIXME" />
<property name="message" value='TODO is preferred to FIXME. e.g. "TODO(johndoe): Refactor when v2 is released."' />
</module>
<module name="RegexpSingleline">
<!-- Checks that TODOs are named. (Actually, just that they are followed
by an open paren.)
-->
<property name="format" value="((//.*)|(\*.*))TODO[^(]" />
<property name="message" value='All TODOs should be named. e.g. "TODO(johndoe): Refactor when v2 is released."' />
</module>
<module name="JavadocPackage">
<!-- Checks that each Java package has a Javadoc file used for commenting.
Only allows a package-info.java, not package.html. -->
</module>
<!-- All Java AST specific tests live under TreeWalker module. -->
<module name="TreeWalker">
<!--
IMPORT CHECKS
-->
<module name="RedundantImport">
<!-- Checks for redundant import statements. -->
<property name="severity" value="error"/>
</module>
<module name="ImportOrder">
<property name="separated" value="true"/>
<property name="severity" value="warning"/>
<property name="groups" value="*,javax,java"/>
<property name="option" value="bottom"/>
<property name="elements" value="IMPORT, STATIC_IMPORT"/>
</module>
<!--
JAVADOC CHECKS
-->
<!-- Checks for Javadoc comments. -->
<!-- See http://checkstyle.sf.net/config_javadoc.html -->
<module name="JavadocMethod">
<property name="scope" value="protected"/>
<property name="severity" value="warning"/>
<property name="allowMissingJavadoc" value="true"/>
<property name="allowMissingParamTags" value="true"/>
<property name="allowMissingReturnTag" value="true"/>
<property name="allowMissingThrowsTags" value="true"/>
<property name="allowThrowsTagsForSubclasses" value="true"/>
<property name="allowUndeclaredRTE" value="true"/>
</module>
<module name="JavadocType">
<property name="scope" value="protected"/>
<property name="severity" value="error"/>
</module>
<module name="JavadocStyle">
<property name="severity" value="warning"/>
</module>
<!--
NAMING CHECKS
-->
<!-- Item 38 - Adhere to generally accepted naming conventions -->
<module name="PackageName">
<!-- Validates identifiers for package names against the
supplied expression. -->
<!-- Here the default checkstyle rule restricts package name parts to
seven characters, this is not in line with common practice at Google.
-->
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]{1,})*$"/>
<property name="severity" value="warning"/>
</module>
<module name="TypeNameCheck">
<!-- Validates static, final fields against the
expression "^[A-Z][a-zA-Z0-9]*$". -->
<metadata name="altname" value="TypeName"/>
<property name="severity" value="warning"/>
</module>
<module name="ConstantNameCheck">
<!-- Validates non-private, static, final fields against the supplied
public/package final fields "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$". -->
<metadata name="altname" value="ConstantName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="false"/>
<property name="format" value="^([A-Z][A-Z0-9]*(_[A-Z0-9]+)*|FLAG_.*)$"/>
<message key="name.invalidPattern"
value="Variable ''{0}'' should be in ALL_CAPS (if it is a constant) or be private (otherwise)."/>
<property name="severity" value="warning"/>
</module>
<module name="StaticVariableNameCheck">
<!-- Validates static, non-final fields against the supplied
expression "^[a-z][a-zA-Z0-9]*_?$". -->
<metadata name="altname" value="StaticVariableName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="true"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*_?$"/>
<property name="severity" value="warning"/>
</module>
<module name="MemberNameCheck">
<!-- Validates non-static members against the supplied expression. -->
<metadata name="altname" value="MemberName"/>
<property name="applyToPublic" value="true"/>
<property name="applyToProtected" value="true"/>
<property name="applyToPackage" value="true"/>
<property name="applyToPrivate" value="true"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
<property name="severity" value="warning"/>
</module>
<module name="MethodNameCheck">
<!-- Validates identifiers for method names. -->
<metadata name="altname" value="MethodName"/>
<property name="format" value="^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$"/>
<property name="severity" value="warning"/>
</module>
<module name="ParameterName">
<!-- Validates identifiers for method parameters against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<module name="LocalFinalVariableName">
<!-- Validates identifiers for local final variables against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<module name="LocalVariableName">
<!-- Validates identifiers for local variables against the
expression "^[a-z][a-zA-Z0-9]*$". -->
<property name="severity" value="warning"/>
</module>
<!--
LENGTH and CODING CHECKS
-->
<module name="LineLength">
<!-- Checks if a line is too long. -->
<property name="max" value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.max}" default="128"/>
<property name="severity" value="error"/>
<!--
The default ignore pattern exempts the following elements:
- import statements
- long URLs inside comments
-->
<property name="ignorePattern"
value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.ignorePattern}"
default="^(package .*;\s*)|(import .*;\s*)|( *(\*|//).*https?://.*)$"/>
</module>
<module name="LeftCurly">
<!-- Checks for placement of the left curly brace ('{'). -->
<property name="severity" value="warning"/>
</module>
<module name="RightCurly">
<!-- Checks right curlies on CATCH, ELSE, and TRY blocks are on
the same line. e.g., the following example is fine:
<pre>
if {
...
} else
</pre>
-->
<!-- This next example is not fine:
<pre>
if {
...
}
else
</pre>
-->
<property name="option" value="same"/>
<property name="severity" value="warning"/>
</module>
<!-- Checks for braces around if and else blocks -->
<module name="NeedBraces">
<property name="severity" value="warning"/>
<property name="elements" value="LITERAL_IF, LITERAL_ELSE, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO"/>
</module>
<module name="UpperEll">
<!-- Checks that long constants are defined with an upper ell.-->
<property name="severity" value="error"/>
</module>
<module name="FallThrough">
<!-- Warn about falling through to the next case statement. Similar to
javac -Xlint:fallthrough, but the check is suppressed if a single-line comment
on the last non-blank line preceding the fallen-into case contains 'fall through' (or
some other variants which we don't publicized to promote consistency).
-->
<property name="reliefPattern"
value="fall through|Fall through|fallthru|Fallthru|falls through|Falls through|fallthrough|Fallthrough|No break|NO break|no break|continue on"/>
<property name="severity" value="error"/>
</module>
<!--
MODIFIERS CHECKS
-->
<module name="ModifierOrder">
<!-- Warn if modifier order is inconsistent with JLS3 8.1.1, 8.3.1, and
8.4.3. The prescribed order is:
public, protected, private, abstract, static, final, transient, volatile,
synchronized, native, strictfp
-->
</module>
<!--
WHITESPACE CHECKS
-->
<module name="WhitespaceAround">
<!-- Checks that various elements are surrounded by whitespace.
This includes most binary operators and keywords followed
by regular or curly braces.
-->
<property name="elements" value="ASSIGN, BAND, BAND_ASSIGN, BOR,
BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN,
EQUAL, GE, GT, LAND, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE,
LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN,
LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS,
MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION,
SL, SL_ASSIGN, SR_ASSIGN, STAR, STAR_ASSIGN"/>
<property name="severity" value="error"/>
</module>
<module name="WhitespaceAfter">
<!-- Checks that commas, semicolons and typecasts are followed by
whitespace.
-->
<property name="elements" value="COMMA, SEMI, TYPECAST"/>
</module>
<module name="NoWhitespaceAfter">
<!-- Checks that there is no whitespace after various unary operators.
Linebreaks are allowed.
-->
<property name="elements" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS,
UNARY_PLUS"/>
<property name="allowLineBreaks" value="true"/>
<property name="severity" value="error"/>
</module>
<module name="NoWhitespaceBefore">
<!-- Checks that there is no whitespace before various unary operators.
Linebreaks are allowed.
-->
<property name="elements" value="SEMI, DOT, POST_DEC, POST_INC"/>
<property name="allowLineBreaks" value="true"/>
<property name="severity" value="error"/>
</module>
<module name="ParenPad">
<!-- Checks that there is no whitespace before close parens or after
open parens.
-->
<property name="severity" value="warning"/>
</module>
</module>
</module>

View file

@ -1,5 +1,5 @@
group = org.xbib
name = net-http
version = 3.3.0
version = 3.3.1
org.gradle.warning.mode = ALL

View file

@ -760,7 +760,7 @@ public class HelloWorldBean {
<![CDATA[
public class Foo {
void bar() {
for (;true;) true; // No Init or Update part, may as well be: while (true)
for (;true;) true; // No Init or Update message, may as well be: while (true)
}
}
]]>

View file

@ -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">
<description>
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.
</description>
<priority>2</priority>
@ -1554,7 +1554,7 @@ public class Foo {
<rule name="EmptyStatementNotInLoop"
language="java"
since="1.5"
message="An empty statement (semicolon) not part of a loop"
message="An empty statement (semicolon) not message of a loop"
class="net.sourceforge.pmd.lang.rule.XPathRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_errorprone.html#emptystatementnotinloop">
<description>

View file

@ -16,7 +16,6 @@ public class HttpsRequestBuilder extends HttpRequestBuilder {
}
public HttpsRequest build() {
this.headers = validateHeaders(headers);
return new HttpsRequest(this);
}
}

View file

@ -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;

View file

@ -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) {
@ -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;

View file

@ -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<HttpRequest> 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<Cookie> 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 extends Request> R as(Class<R> cl) {
return (R) this;
}
public List<Part> 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<HttpRequest> completableFuture) {
this.completableFuture = completableFuture;
return this;
}
public CompletableFuture<HttpRequest> getCompletableFuture() {
return completableFuture;
}
public void setResponseListener(ResponseListener<HttpResponse> 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());
}
}

View file

@ -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<Cookie> cookies;
protected HttpMethod httpMethod;
protected HttpHeaders headers;
protected HttpVersion httpVersion;
protected final List<String> 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<HttpResponse> responseListener;
protected ExceptionListener exceptionListener;
protected TimeoutListener timeoutListener;
protected long timeoutMillis;
protected final List<Part> 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<String, String> 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<String, Object> parameters) {
parameters.forEach(this::addParameter);
@Override
public HttpRequestBuilder setParameters(Map<String, Object> map) {
super.setParameters(map);
return this;
}
@SuppressWarnings("unchecked")
@Override
public HttpRequestBuilder addParameter(String name, Object value) {
Objects.requireNonNull(name);
Objects.requireNonNull(value);
Collection<Object> collection;
if (!(value instanceof Collection)) {
collection = Collections.singletonList(value);
} else {
collection = (Collection<Object>) 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<org.xbib.net.http.client.HttpResponse> 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<HttpResponse> 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;
}
}

View file

@ -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;
}
}

View file

@ -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,21 +105,23 @@ public class Http1Interaction extends BaseInteraction {
// headers
request.getHeaders().entries().forEach(p -> fullHttpRequest.headers().add(p.getKey(), p.getValue()));
// file upload
if (nettyHttpClient.getClientConfig().isFileUploadEnabled()) {
HttpDataFactory httpDataFactory = new DefaultHttpDataFactory();
channel.attr(NettyHttpClientConfig.ATTRIBUTE_HTTP_DATAFACTORY).set(httpDataFactory);
HttpPostRequestEncoder httpPostRequestEncoder = null;
try {
if (!request.getParts().isEmpty()) {
if (!request.getMessages().isEmpty()) {
httpPostRequestEncoder = new HttpPostRequestEncoder(httpDataFactory,
fullHttpRequest,
true,
StandardCharsets.UTF_8,
HttpPostRequestEncoder.EncoderMode.RFC1738);
for (Part part : request.getParts()) {
Path path = part.getPath();
for (Message message : request.getMessages()) {
Path path = message.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 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);
@ -137,8 +139,9 @@ public class Http1Interaction extends BaseInteraction {
channel.flush();
} catch (HttpPostRequestEncoder.ErrorDataEncoderException e) {
throw new IOException(e);
} finally {
channel.attr(NettyHttpClientConfig.ATTRIBUTE_HTTP_DATAFACTORY).set(httpDataFactory);
}
} else {
channel.write(fullHttpRequest);
}
return this;
}

View file

@ -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<Message> getMessages() {
return builder.messages;
}
@SuppressWarnings("unchecked")
@Override
public <R extends Request> R as(Class<R> 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<Cookie> cookies() {
return builder.cookies;
}
public HttpRequest setCompletableFuture(CompletableFuture<HttpRequest> completableFuture) {
builder.completableFuture = completableFuture;
return this;
}
public CompletableFuture<HttpRequest> getCompletableFuture() {
return builder.completableFuture;
}
public void setResponseListener(ResponseListener<HttpResponse> responseListener) {
builder.responseListener = responseListener;
}
public ResponseListener<HttpResponse> 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 +
"]";
}
}

View file

@ -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<Message> messages;
protected boolean followRedirect;
protected int maxRedirects;
protected int redirectCount;
protected final Collection<Cookie> cookies;
protected String contentType;
protected String userAgent;
protected boolean isGzipEnabled;
protected boolean isKeepAliveEnabled;
protected boolean isBackoffEnabled;
protected BackOff backOff;
protected ResponseListener<HttpResponse> responseListener;
protected ExceptionListener exceptionListener;
protected TimeoutListener timeoutListener;
protected long timeoutMillis;
protected CompletableFuture<HttpRequest> 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<String, String> 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<String, Object> map) {
map.forEach(this::addParameter);
return this;
}
@SuppressWarnings("unchecked")
public BaseHttpRequestBuilder addParameter(String name, Object value) {
Objects.requireNonNull(name);
Objects.requireNonNull(value);
Collection<Object> collection;
if (!(value instanceof Collection)) {
collection = Collections.singletonList(value);
} else {
collection = (Collection<Object>) 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<HttpResponse> 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");
}
}

View file

@ -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<Message> getMessages();
boolean isBackOffEnabled();
BackOff getBackOff();
Collection<Cookie> cookies();
}

View file

@ -19,7 +19,7 @@ public interface HttpRequestBuilder {
HttpRequestBuilder setBody(ByteBuffer byteBuffer);
HttpRequestBuilder addPart(Part part);
HttpRequestBuilder addMessage(Message message);
HttpRequest build() throws UnmappableCharacterException, MalformedInputException;
}

View file

@ -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,7 +16,7 @@ public class Part {
private final Charset charset;
public Part(String contentType,
public Message(String contentType,
String contentTransferEncoding,
String name,
Path path,
@ -46,4 +47,9 @@ public class Part {
public Charset getCharset() {
return charset;
}
@Override
public String toString() {
return "Message[name=" + name + ",path=" + path + "]";
}
}

View file

@ -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,

View file

@ -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);

View file

@ -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,

View file

@ -33,7 +33,7 @@ public class Https2ChildChannelInitializer extends ChannelInitializer<Channel> {
@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));

View file

@ -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()

View file

@ -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 extends Request> R as(Class<R> type) {
Objects.requireNonNull(type);

View file

@ -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,6 +41,8 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder {
}
public HttpRequestBuilder setFullHttpRequest(FullHttpRequest fullHttpRequest) {
if (fullHttpRequest != null) {
this.httpRequest = fullHttpRequest;
setVersion(HttpVersion.valueOf(fullHttpRequest.protocolVersion().text()));
setMethod(HttpMethod.valueOf(fullHttpRequest.method().name()));
setRequestURI(fullHttpRequest.uri());
@ -47,6 +51,7 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder {
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();
}
}
}
}

View file

@ -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,

View file

@ -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;
/**
@ -109,10 +109,27 @@ public class NettyHttpServer implements HttpServer {
.childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, socketConfig.getConnectTimeoutMillis())
.childHandler(new ChannelInitializer<>() {
@Override
protected void initChannel(Channel ch) {
AttributeKey<HttpAddress> key = AttributeKey.valueOf("_address");
ch.attr(key).set(httpAddress);
createChannelInitializer(httpAddress).init(ch, getServer(), builder.nettyCustomizer);
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()) {
@ -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() {
Callable<?> callable = (Callable<Object>) () -> {
HttpRouter router = builder.application.getRouter();
router.route(builder.application, requestBuilder, responseBuilder);
return true;
}
@Override
public void release() {
requestBuilder.release();
responseBuilder.release();
}
};
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() {
Callable<?> callable = (Callable<Object>) () -> {
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();
}
};
builder.application.getExecutor().execute(callableReleasable);
builder.application.getExecutor().execute(callable);
}
@Override

View file

@ -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<HttpAddress> ATTRIBUTE_KEY_HTTP_ADDRESS = AttributeKey.valueOf("_address");
public static final AttributeKey<HttpAddress> ATTRIBUTE_HTTP_ADDRESS = AttributeKey.valueOf("_address");
public static final AttributeKey<HttpRequestBuilder> ATTRIBUTE_HTTP_REQUEST = AttributeKey.valueOf("_request");
public static final AttributeKey<HttpResponseBuilder> ATTRIBUTE_HTTP_RESPONSE = AttributeKey.valueOf("response");
public static final AttributeKey<HttpDataFactory> ATTRIBUTE_HTTP_DATAFACTORY = AttributeKey.valueOf("_datafactory");
/**
* Enforce the transport class name if many transport providers are given.

View file

@ -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,

View file

@ -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<HttpObjec
httpRequest = (HttpRequest) httpObject;
// peek into request if we have a POST request
if (httpRequest.method() == HttpMethod.POST) {
HttpDataFactory factory = new DefaultHttpDataFactory(nettyHttpServer.getNettyHttpServerConfig().getFileUploadDiskThreshold());
HttpDataFactory factory = new DefaultHttpDataFactory(nettyHttpServer.getNettyHttpServerConfig()
.getFileUploadDiskThreshold());
ctx.channel().attr(NettyHttpServerConfig.ATTRIBUTE_HTTP_DATAFACTORY).set(factory);
httpDecoder = new HttpPostRequestDecoder(factory, httpRequest);
}
}
@ -68,12 +69,6 @@ public class HttpFileUploadHandler extends SimpleChannelInboundHandler<HttpObjec
} catch (HttpPostRequestDecoder.EndOfDataDecoderException e) {
logger.log(Level.FINEST, "end of data decoder exception");
}
if (chunk instanceof LastHttpContent) {
logger.log(Level.FINEST, "destroying HTTP decode");
httpDecoder.destroy();
}
} else {
logger.log(Level.FINEST, "not a HttpContent: " );
}
}
}
@ -87,7 +82,7 @@ public class HttpFileUploadHandler extends SimpleChannelInboundHandler<HttpObjec
protected void requestReceived(ChannelHandlerContext ctx,
HttpRequest httpRequest,
FileUpload fileUpload) {
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()
.setChannelHandlerContext(ctx);

View file

@ -49,7 +49,7 @@ public class Http2ChannelInitializer implements HttpChannelInitializer {
@Override
public void init(Channel channel, NettyHttpServer nettyHttpServer, NettyCustomizer customizer) {
final HttpAddress httpAddress = channel.attr(NettyHttpServerConfig.ATTRIBUTE_KEY_HTTP_ADDRESS).get();
final HttpAddress httpAddress = channel.attr(NettyHttpServerConfig.ATTRIBUTE_HTTP_ADDRESS).get();
final NettyHttpServerConfig nettyHttpServerConfig = nettyHttpServer.getNettyHttpServerConfig();
ChannelPipeline pipeline = channel.pipeline();
if (nettyHttpServerConfig.isDebug()) {

View file

@ -28,7 +28,7 @@ public class Http2ChildChannelInitializer extends ChannelInitializer<Channel> {
@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));

View file

@ -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();
}
}
}

View file

@ -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)

View file

@ -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())

View file

@ -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())

View file

@ -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<org.xbib.net.http.server.Part> 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<org.xbib.net.http.server.Message> 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() +

View file

@ -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)

View file

@ -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())

View file

@ -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();
}
}

View file

@ -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);

View file

@ -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() {
Callable<?> callable = (Callable<Object>) () -> {
HttpRouter router = builder.application.getRouter();
router.route(builder.application, requestBuilder, responseBuilder);
return true;
}
@Override
public void release() {
requestBuilder.release();
responseBuilder.release();
}
};
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() {
Callable<?> callable = (Callable<Object>) () -> {
HttpRouter router = builder.application.getRouter();
router.routeStatus(responseStatus, httpServerContext);
return true;
}
@Override
public void release() {
requestBuilder.release();
responseBuilder.release();
}
};
builder.application.getExecutor().execute(callableReleasable);
builder.application.getExecutor().execute(callable);
}
@Override

View file

@ -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() {
Callable<?> callableReleasable = (Callable<Object>) () -> {
HttpRouter router = builder.application.getRouter();
router.route(builder.application, requestBuilder, responseBuilder);
return true;
}
@Override
public void release() {
requestBuilder.release();
responseBuilder.release();
}
};
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() {
Callable<?> callable = (Callable<Object>) () -> {
HttpRouter router = builder.application.getRouter();
router.routeStatus(responseStatus, httpServerContext);
return true;
}
@Override
public void release() {
requestBuilder.release();
responseBuilder.release();
}
};
builder.application.getExecutor().execute(callableReleasable);
builder.application.getExecutor().execute(callable);
}
@Override

View file

@ -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"));

View file

@ -87,8 +87,8 @@ public abstract class BaseHttpRequest implements HttpRequest {
}
@Override
public List<Part> getParts() {
return builder.parts;
public List<Message> getMessages() {
return builder.messages;
}
@Override

View file

@ -51,11 +51,11 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder {
protected boolean done;
protected List<Part> parts;
protected List<Message> 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;
}
}

View file

@ -7,4 +7,9 @@ public abstract class BaseHttpResponse implements HttpResponse {
protected BaseHttpResponse(BaseHttpResponseBuilder builder) {
this.builder = builder;
}
@Override
public void release() {
builder.release();
}
}

View file

@ -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<Part> getParts();
List<Message> getMessages();
Attributes getAttributes();
}

View file

@ -31,7 +31,7 @@ public interface HttpRequestBuilder {
HttpRequestBuilder addHeader(String name, String value);
HttpRequestBuilder addPart(Part part);
HttpRequestBuilder addPart(Message message);
URL getBaseURL();

View file

@ -4,4 +4,5 @@ import org.xbib.net.Response;
public interface HttpResponse extends Response {
void release();
}

View file

@ -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,7 +15,7 @@ public class Part {
private final ByteBuffer byteBuffer;
public Part(String contentType,
public Message(String contentType,
String contentTransferEncoding,
String name,
Path path,
@ -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 + "]";
}
}

View file

@ -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);
}
}

View file

@ -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

View file

@ -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();
}
}
}

View file

@ -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<T> extends Callable<T>, Releasable {
}

View file

@ -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;
}