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 group = org.xbib
name = net-http name = net-http
version = 3.3.0 version = 3.3.1
org.gradle.warning.mode = ALL org.gradle.warning.mode = ALL

View file

@ -760,7 +760,7 @@ public class HelloWorldBean {
<![CDATA[ <![CDATA[
public class Foo { public class Foo {
void bar() { 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." message="Avoid using a branching statement as the last in a loop."
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_errorprone.html#avoidbranchingstatementaslastinloop"> externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_errorprone.html#avoidbranchingstatementaslastinloop">
<description> <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. Ensure that the usage is not a bug, or consider using another approach.
</description> </description>
<priority>2</priority> <priority>2</priority>
@ -1554,7 +1554,7 @@ public class Foo {
<rule name="EmptyStatementNotInLoop" <rule name="EmptyStatementNotInLoop"
language="java" language="java"
since="1.5" 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" class="net.sourceforge.pmd.lang.rule.XPathRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_errorprone.html#emptystatementnotinloop"> externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_errorprone.html#emptystatementnotinloop">
<description> <description>

View file

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

View file

@ -6,7 +6,6 @@ import io.netty.util.AttributeKey;
import java.io.InputStream; import java.io.InputStream;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactory;

View file

@ -270,7 +270,8 @@ public abstract class BaseInteraction implements Interaction {
protected abstract Channel nextChannel() throws IOException; 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) { if (httpResponse == null) {
return null; return null;
} }
@ -282,13 +283,7 @@ public abstract class BaseInteraction implements Interaction {
if (request.canRedirect()) { if (request.canRedirect()) {
int status = httpResponse.getStatus().code(); int status = httpResponse.getStatus().code();
switch (status) { switch (status) {
case 300: case 300, 301, 302, 303, 305, 307, 308 -> {
case 301:
case 302:
case 303:
case 305:
case 307:
case 308:
String location = httpResponse.getHeaders().get(HttpHeaderNames.LOCATION); String location = httpResponse.getHeaders().get(HttpHeaderNames.LOCATION);
location = new PercentDecoder(StandardCharsets.UTF_8.newDecoder()).decode(location); location = new PercentDecoder(StandardCharsets.UTF_8.newDecoder()).decode(location);
if (location != null) { if (location != null) {
@ -298,7 +293,7 @@ public abstract class BaseInteraction implements Interaction {
HttpRequestBuilder newHttpRequestHttpRequestBuilder = HttpRequest.builder(method, request) HttpRequestBuilder newHttpRequestHttpRequestBuilder = HttpRequest.builder(method, request)
.setURL(redirUrl); .setURL(redirUrl);
request.getURL().getQueryParams().forEach(pair -> request.getURL().getQueryParams().forEach(pair ->
newHttpRequestHttpRequestBuilder.addParameter(pair.getKey(), pair.getValue()) newHttpRequestHttpRequestBuilder.addParameter(pair.getKey(), pair.getValue())
); );
request.cookies().forEach(newHttpRequestHttpRequestBuilder::addCookie); request.cookies().forEach(newHttpRequestHttpRequestBuilder::addCookie);
HttpRequest newHttpRequest = newHttpRequestHttpRequestBuilder.build(); HttpRequest newHttpRequest = newHttpRequestHttpRequestBuilder.build();
@ -311,9 +306,9 @@ public abstract class BaseInteraction implements Interaction {
logger.log(Level.FINE, "redirect url: " + redirUrl); logger.log(Level.FINE, "redirect url: " + redirUrl);
return newHttpRequest; return newHttpRequest;
} }
break; }
default: default -> {
break; }
} }
} }
} catch (MalformedInputException | UnmappableCharacterException e) { } catch (MalformedInputException | UnmappableCharacterException e) {
@ -331,20 +326,13 @@ public abstract class BaseInteraction implements Interaction {
// push promise or something else // push promise or something else
return null; return null;
} }
if (request.isBackOff()) { if (request.isBackOffEnabled()) {
BackOff backOff = request.getBackOff() != null ? BackOff backOff = request.getBackOff() != null ?
request.getBackOff() : request.getBackOff() :
nettyHttpClient.getClientConfig().getBackOff(); nettyHttpClient.getClientConfig().getBackOff();
int status = httpResponse.getStatus ().code(); int status = httpResponse.getStatus ().code();
switch (status) { switch (status) {
case 403: case 403, 404, 500, 502, 503, 504, 507, 509 -> {
case 404:
case 500:
case 502:
case 503:
case 504:
case 507:
case 509:
if (backOff != null) { if (backOff != null) {
long millis = backOff.nextBackOffMillis(); long millis = backOff.nextBackOffMillis();
if (millis != BackOff.STOP) { if (millis != BackOff.STOP) {
@ -357,9 +345,9 @@ public abstract class BaseInteraction implements Interaction {
return request; return request;
} }
} }
break; }
default: default -> {
break; }
} }
} }
return null; return null;

View file

@ -2,208 +2,20 @@ package org.xbib.net.http.client.netty;
import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.PooledByteBufAllocator; import io.netty.buffer.PooledByteBufAllocator;
import java.io.Closeable; import io.netty.handler.codec.http2.HttpConversionUtil;
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 org.xbib.net.http.HttpMethod; import org.xbib.net.http.HttpMethod;
import org.xbib.net.http.HttpVersion; import org.xbib.net.http.client.BaseHttpRequest;
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;
/** /**
* HTTP client request. * Netty HTTP client request.
*/ */
public class HttpRequest implements org.xbib.net.http.client.HttpRequest, Closeable { public class HttpRequest extends BaseHttpRequest {
private final HttpRequestBuilder builder;
private CompletableFuture<HttpRequest> completableFuture;
private int redirectCount;
protected HttpRequest(HttpRequestBuilder builder) { protected HttpRequest(HttpRequestBuilder builder) {
this.builder = builder; super(builder);
} String scheme = builder.getUrl().getScheme();
if (this.builder.getVersion().majorVersion() == 2) {
@Override this.builder.addHeader(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text().toString(), scheme);
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);
}
} }
} }
@ -247,18 +59,19 @@ public class HttpRequest implements org.xbib.net.http.client.HttpRequest, Closea
return builder(PooledByteBufAllocator.DEFAULT, httpMethod); return builder(PooledByteBufAllocator.DEFAULT, httpMethod);
} }
public static HttpRequestBuilder builder(HttpMethod httpMethod, HttpRequest httpRequest) { public static HttpRequestBuilder builder(ByteBufAllocator allocator, HttpMethod httpMethod) {
return builder(PooledByteBufAllocator.DEFAULT, httpMethod) return new HttpRequestBuilder(allocator)
.setVersion(httpRequest.builder.httpVersion) .setMethod(httpMethod);
.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) { public static HttpRequestBuilder builder(HttpMethod httpMethod, HttpRequest httpRequest) {
return new HttpRequestBuilder(allocator).setMethod(httpMethod); 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.buffer.ByteBufAllocator;
import io.netty.handler.codec.http.HttpUtil; 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.ByteBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; 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.Map;
import java.util.Objects;
import org.xbib.net.Parameter;
import org.xbib.net.ParameterBuilder; import org.xbib.net.ParameterBuilder;
import org.xbib.net.URL; import org.xbib.net.URL;
import org.xbib.net.URLBuilder;
import org.xbib.net.http.HttpAddress; 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.HttpHeaders;
import org.xbib.net.http.HttpMethod; import org.xbib.net.http.HttpMethod;
import org.xbib.net.http.HttpVersion; 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.ExceptionListener;
import org.xbib.net.http.client.HttpResponse; import org.xbib.net.http.client.Message;
import org.xbib.net.http.client.Part;
import org.xbib.net.http.client.ResponseListener; import org.xbib.net.http.client.ResponseListener;
import org.xbib.net.http.client.TimeoutListener; import org.xbib.net.http.client.TimeoutListener;
import org.xbib.net.http.cookie.Cookie;
public class HttpRequestBuilder implements org.xbib.net.http.client.HttpRequestBuilder { public class HttpRequestBuilder extends BaseHttpRequestBuilder {
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";
protected final ByteBufAllocator allocator; 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() { protected HttpRequestBuilder() {
this(ByteBufAllocator.DEFAULT); this(ByteBufAllocator.DEFAULT);
} }
protected HttpRequestBuilder(ByteBufAllocator allocator) { protected HttpRequestBuilder(ByteBufAllocator allocator) {
super();
this.allocator = allocator; this.allocator = allocator;
this.httpMethod = HttpMethod.GET;
this.httpVersion = HttpVersion.HTTP_1_1;
this.userAgent = UserAgent.getUserAgent(); 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 @Override
public HttpRequestBuilder setAddress(HttpAddress httpAddress) { public HttpRequestBuilder setAddress(HttpAddress httpAddress) {
this.httpAddress = httpAddress; super.setAddress(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();
return this; return this;
} }
public HttpRequestBuilder setURL(String url) {
return setURL(URL.from(url));
}
@Override @Override
public HttpRequestBuilder setURL(URL url) { 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; return this;
} }
@Override @Override
public HttpRequestBuilder setRequestPath(String requestPath) { public HttpRequestBuilder setRequestPath(String requestPath) {
this.requestPath = requestPath; super.setRequestPath(requestPath);
return this; return this;
} }
public HttpRequestBuilder setMethod(HttpMethod httpMethod) { @Override
this.httpMethod = httpMethod; public HttpRequestBuilder setHeaders(HttpHeaders httpHeaders) {
return this; super.setHeaders(httpHeaders);
}
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;
return this; return this;
} }
@Override
public HttpRequestBuilder addHeader(String name, String value) { public HttpRequestBuilder addHeader(String name, String value) {
this.headers.add(name, value); super.addHeader(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());
return this; return this;
} }
@ -207,114 +92,63 @@ public class HttpRequestBuilder implements org.xbib.net.http.client.HttpRequestB
return this; return this;
} }
public HttpRequestBuilder setParameters(Map<String, Object> parameters) { @Override
parameters.forEach(this::addParameter); public HttpRequestBuilder setParameters(Map<String, Object> map) {
super.setParameters(map);
return this; return this;
} }
@SuppressWarnings("unchecked") @Override
public HttpRequestBuilder addParameter(String name, Object value) { public HttpRequestBuilder addParameter(String name, Object value) {
Objects.requireNonNull(name); super.addParameter(name, value);
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);
return this; return this;
} }
@Override @Override
public HttpRequestBuilder setBody(ByteBuffer byteBuffer) { public HttpRequestBuilder setBody(ByteBuffer byteBuffer) {
this.body = byteBuffer; super.setBody(byteBuffer);
return this; return this;
} }
@Override @Override
public HttpRequestBuilder addPart(Part part) { public HttpRequestBuilder addMessage(Message message) {
parts.add(part); super.addMessage(message);
return this; return this;
} }
public HttpRequestBuilder addCookie(Cookie cookie) { @Override
cookies.add(cookie); public HttpRequestBuilder content(ByteBuffer byteBuffer) {
super.content(byteBuffer);
return this; return this;
} }
public HttpRequestBuilder acceptGzip(boolean gzip) { @Override
this.gzip = gzip; public HttpRequestBuilder content(CharSequence charSequence, CharSequence contentType, Charset charset) {
return this; super.content(charSequence, contentType, charset);
}
public HttpRequestBuilder keepAlive(boolean keepalive) {
this.keepalive = keepalive;
return this; return this;
} }
@Override
public HttpRequestBuilder setFollowRedirect(boolean followRedirect) { public HttpRequestBuilder setFollowRedirect(boolean followRedirect) {
this.followRedirect = followRedirect; super.setFollowRedirect(followRedirect);
return this; return this;
} }
public HttpRequestBuilder setMaxRedirects(int maxRedirects) { @Override
this.maxRedirects = maxRedirects; public HttpRequestBuilder setResponseListener(ResponseListener<org.xbib.net.http.client.HttpResponse> responseListener) {
super.setResponseListener(responseListener);
return this; return this;
} }
public HttpRequestBuilder enableBackOff(boolean enableBackOff) { @Override
this.enableBackOff = enableBackOff; public HttpRequestBuilder setExceptionListener(ExceptionListener exceptionListener) {
super.setExceptionListener(exceptionListener);
return this; return this;
} }
public HttpRequestBuilder setBackOff(BackOff backOff) { @Override
this.backOff = backOff; public HttpRequestBuilder setTimeoutListener(TimeoutListener timeoutListener, long timeoutMillis) {
return this; super.setTimeoutListener(timeoutListener, timeoutMillis);
}
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");
return this; return this;
} }
@ -322,113 +156,12 @@ public class HttpRequestBuilder implements org.xbib.net.http.client.HttpRequestB
if (charSequence == null) { if (charSequence == null) {
return this; return this;
} }
// use current content type charset or UTF-8
content(charSequence.toString().getBytes(HttpUtil.getCharset(contentType, StandardCharsets.UTF_8)), contentType.toString()); content(charSequence.toString().getBytes(HttpUtil.getCharset(contentType, StandardCharsets.UTF_8)), contentType.toString());
return this; 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() { public HttpRequest build() {
this.headers = validateHeaders(headers);
return new HttpRequest(this); 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 HttpVersion poolVersion = HttpVersion.HTTP_1_1;
private Boolean poolSecure = false; private boolean poolSecure = false;
private Http2Settings http2Settings = Http2Settings.defaultSettings(); private Http2Settings http2Settings = Http2Settings.defaultSettings();
@ -99,9 +99,11 @@ public class NettyHttpClientConfig {
private BackOff backOff = BackOff.ZERO_BACKOFF; 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() { public NettyHttpClientConfig() {
this.byteBufAllocator = ByteBufAllocator.DEFAULT; this.byteBufAllocator = ByteBufAllocator.DEFAULT;
@ -343,7 +345,7 @@ public class NettyHttpClientConfig {
return this; return this;
} }
public Boolean isChunkWriteEnabled() { public boolean isChunkWriteEnabled() {
return isChunkWriteEnabled; return isChunkWriteEnabled;
} }
@ -352,7 +354,16 @@ public class NettyHttpClientConfig {
return this; return this;
} }
public Boolean isObjectAggregationEnabled() { public boolean isObjectAggregationEnabled() {
return 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.HttpAddress;
import org.xbib.net.http.HttpHeaders; import org.xbib.net.http.HttpHeaders;
import org.xbib.net.http.HttpResponseStatus; 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.client.netty.NettyHttpClientConfig;
import org.xbib.net.http.cookie.Cookie; import org.xbib.net.http.cookie.Cookie;
import org.xbib.net.http.client.cookie.CookieDecoder; import org.xbib.net.http.client.cookie.CookieDecoder;
@ -105,40 +105,43 @@ public class Http1Interaction extends BaseInteraction {
// headers // headers
request.getHeaders().entries().forEach(p -> fullHttpRequest.headers().add(p.getKey(), p.getValue())); request.getHeaders().entries().forEach(p -> fullHttpRequest.headers().add(p.getKey(), p.getValue()));
// file upload // file upload
HttpDataFactory httpDataFactory = new DefaultHttpDataFactory(); if (nettyHttpClient.getClientConfig().isFileUploadEnabled()) {
HttpPostRequestEncoder httpPostRequestEncoder = null; HttpDataFactory httpDataFactory = new DefaultHttpDataFactory();
try {
if (!request.getParts().isEmpty()) {
httpPostRequestEncoder = new HttpPostRequestEncoder(httpDataFactory,
fullHttpRequest,
true,
StandardCharsets.UTF_8,
HttpPostRequestEncoder.EncoderMode.RFC1738);
for (Part part : request.getParts()) {
Path path = part.getPath();
if (Files.exists(path)) {
FileUpload fileUpload = httpDataFactory.createFileUpload(fullHttpRequest, part.getName(),
path.toFile().getName(), part.getContentType(), part.getContentTransferEncoding(),
part.getCharset(), Files.size(path));
fileUpload.setContent(path.toFile());
logger.log(Level.FINEST, "HTTP FORM file upload = " + fileUpload);
httpPostRequestEncoder.addBodyHttpData(fileUpload);
} else {
logger.log(Level.WARNING, " does not exist : " + path);
}
}
io.netty.handler.codec.http.HttpRequest httpRequest = httpPostRequestEncoder.finalizeRequest();
channel.write(httpRequest);
}
channel.write(fullHttpRequest);
if (httpPostRequestEncoder != null && httpPostRequestEncoder.isChunked()) {
channel.write(httpPostRequestEncoder);
}
channel.flush();
} catch (HttpPostRequestEncoder.ErrorDataEncoderException e) {
throw new IOException(e);
} finally {
channel.attr(NettyHttpClientConfig.ATTRIBUTE_HTTP_DATAFACTORY).set(httpDataFactory); channel.attr(NettyHttpClientConfig.ATTRIBUTE_HTTP_DATAFACTORY).set(httpDataFactory);
HttpPostRequestEncoder httpPostRequestEncoder = null;
try {
if (!request.getMessages().isEmpty()) {
httpPostRequestEncoder = new HttpPostRequestEncoder(httpDataFactory,
fullHttpRequest,
true,
StandardCharsets.UTF_8,
HttpPostRequestEncoder.EncoderMode.RFC1738);
for (Message message : request.getMessages()) {
Path path = message.getPath();
if (Files.exists(path)) {
FileUpload fileUpload = httpDataFactory.createFileUpload(fullHttpRequest, message.getName(),
path.toFile().getName(), message.getContentType(), message.getContentTransferEncoding(),
message.getCharset(), Files.size(path));
fileUpload.setContent(path.toFile());
logger.log(Level.FINEST, "HTTP FORM file upload = " + fileUpload);
httpPostRequestEncoder.addBodyHttpData(fileUpload);
} else {
logger.log(Level.WARNING, " does not exist : " + path);
}
}
io.netty.handler.codec.http.HttpRequest httpRequest = httpPostRequestEncoder.finalizeRequest();
channel.write(httpRequest);
}
channel.write(fullHttpRequest);
if (httpPostRequestEncoder != null && httpPostRequestEncoder.isChunked()) {
channel.write(httpPostRequestEncoder);
}
channel.flush();
} catch (HttpPostRequestEncoder.ErrorDataEncoderException e) {
throw new IOException(e);
}
} else {
channel.write(fullHttpRequest);
} }
return this; return this;
} }

View file

@ -4,11 +4,24 @@ import java.net.InetSocketAddress;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.CharBuffer; import java.nio.CharBuffer;
import java.nio.charset.Charset; 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.ParameterBuilder;
import org.xbib.net.Request;
import org.xbib.net.URL; 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.HttpHeaders;
import org.xbib.net.http.HttpMethod; import org.xbib.net.http.HttpMethod;
import org.xbib.net.http.HttpVersion; import org.xbib.net.http.HttpVersion;
import org.xbib.net.http.cookie.Cookie;
public abstract class BaseHttpRequest implements HttpRequest { public abstract class BaseHttpRequest implements HttpRequest {
@ -16,6 +29,48 @@ public abstract class BaseHttpRequest implements HttpRequest {
protected BaseHttpRequest(BaseHttpRequestBuilder builder) { protected BaseHttpRequest(BaseHttpRequestBuilder builder) {
this.builder = 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 @Override
@ -30,6 +85,15 @@ public abstract class BaseHttpRequest implements HttpRequest {
@Override @Override
public URL getBaseURL() { 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; return builder.url;
} }
@ -49,7 +113,7 @@ public abstract class BaseHttpRequest implements HttpRequest {
} }
@Override @Override
public ParameterBuilder getParameters() { public ParameterBuilder getParameterBuilder() {
return builder.parameterBuilder; return builder.parameterBuilder;
} }
@ -62,4 +126,128 @@ public abstract class BaseHttpRequest implements HttpRequest {
public CharBuffer getBodyAsChars(Charset charset) { public CharBuffer getBodyAsChars(Charset charset) {
return builder.byteBuffer != null ? charset.decode(builder.byteBuffer) : null; 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.net.InetSocketAddress;
import java.nio.ByteBuffer; 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.ParameterBuilder;
import org.xbib.net.URL; import org.xbib.net.URL;
import org.xbib.net.http.HttpAddress; 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.HttpHeaders;
import org.xbib.net.http.HttpMethod; import org.xbib.net.http.HttpMethod;
import org.xbib.net.http.HttpVersion; import org.xbib.net.http.HttpVersion;
import org.xbib.net.http.cookie.Cookie;
public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder { 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() { 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) { public BaseHttpRequestBuilder setVersion(HttpVersion httpVersion) {
@ -45,6 +116,11 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder {
return this; return this;
} }
public BaseHttpRequestBuilder setVersion(String httpVersion) {
setVersion(HttpVersion.valueOf(httpVersion));
return this;
}
public HttpVersion getVersion() { public HttpVersion getVersion() {
return httpVersion; return httpVersion;
} }
@ -63,22 +139,51 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder {
return this; return this;
} }
public BaseHttpRequestBuilder setHeaders(Map<String, String> headers) {
headers.forEach(this::addHeader);
return this;
}
public BaseHttpRequestBuilder addHeader(String key, String value) { public BaseHttpRequestBuilder addHeader(String key, String value) {
this.httpHeaders.add(key, value); this.httpHeaders.add(key, value);
return this; 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 @Override
public BaseHttpRequestBuilder setAddress(HttpAddress httpAddress) { public BaseHttpRequestBuilder setAddress(HttpAddress httpAddress) {
this.httpAddress = httpAddress; this.httpAddress = httpAddress;
return this; return this;
} }
@Override @Override
public BaseHttpRequestBuilder setURL(URL url) { public BaseHttpRequestBuilder setURL(URL url) {
this.url = url; this.url = url;
return this; return this;
} }
public BaseHttpRequestBuilder setURL(String url) {
return setURL(URL.from(url));
}
public URL getUrl() {
return this.url;
}
@Override @Override
public BaseHttpRequestBuilder setRequestPath(String requestPath) { public BaseHttpRequestBuilder setRequestPath(String requestPath) {
this.requestPath = requestPath; this.requestPath = requestPath;
@ -91,12 +196,50 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder {
return this; 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 @Override
public BaseHttpRequestBuilder setBody(ByteBuffer byteBuffer) { public BaseHttpRequestBuilder setBody(ByteBuffer byteBuffer) {
this.byteBuffer = byteBuffer; this.byteBuffer = byteBuffer;
return this; return this;
} }
@Override
public BaseHttpRequestBuilder addMessage(Message message) {
messages.add(message);
return this;
}
public BaseHttpRequestBuilder setLocalAddress(InetSocketAddress localAddress) { public BaseHttpRequestBuilder setLocalAddress(InetSocketAddress localAddress) {
this.localAddress = localAddress; this.localAddress = localAddress;
return this; return this;
@ -121,4 +264,142 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder {
this.requestId = requestId; this.requestId = requestId;
return this; 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.ByteBuffer;
import java.nio.CharBuffer; import java.nio.CharBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Collection;
import java.util.List;
import org.xbib.net.ParameterBuilder; import org.xbib.net.ParameterBuilder;
import org.xbib.net.Request; import org.xbib.net.Request;
import org.xbib.net.URL; import org.xbib.net.URL;
import org.xbib.net.http.HttpHeaders; import org.xbib.net.http.HttpHeaders;
import org.xbib.net.http.HttpMethod; import org.xbib.net.http.HttpMethod;
import org.xbib.net.http.HttpVersion; import org.xbib.net.http.HttpVersion;
import org.xbib.net.http.cookie.Cookie;
public interface HttpRequest extends Request { public interface HttpRequest extends Request {
@ -20,10 +23,18 @@ public interface HttpRequest extends Request {
HttpHeaders getHeaders(); HttpHeaders getHeaders();
ParameterBuilder getParameters(); ParameterBuilder getParameterBuilder();
ByteBuffer getBody(); ByteBuffer getBody();
CharBuffer getBodyAsChars(Charset charset); 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 setBody(ByteBuffer byteBuffer);
HttpRequestBuilder addPart(Part part); HttpRequestBuilder addMessage(Message message);
HttpRequest build() throws UnmappableCharacterException, MalformedInputException; 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.charset.Charset;
import java.nio.file.Path; import java.nio.file.Path;
import static java.nio.charset.StandardCharsets.UTF_8;
public class Part { public class Message {
private final String contentType; private final String contentType;
@ -15,11 +16,11 @@ public class Part {
private final Charset charset; private final Charset charset;
public Part(String contentType, public Message(String contentType,
String contentTransferEncoding, String contentTransferEncoding,
String name, String name,
Path path, Path path,
Charset charset) { Charset charset) {
this.contentType = contentType; this.contentType = contentType;
this.contentTransferEncoding = contentTransferEncoding; this.contentTransferEncoding = contentTransferEncoding;
this.name = name; this.name = name;
@ -46,4 +47,9 @@ public class Part {
public Charset getCharset() { public Charset getCharset() {
return charset; return charset;
} }
@Override
public String toString() {
return "Message[name=" + name + ",path=" + path + "]";
}
} }

View file

@ -47,7 +47,7 @@ public class Https1ChannelInitializer implements HttpChannelInitializer {
@Override @Override
public void init(Channel channel, NettyHttpServer nettyHttpServer, NettyCustomizer customizer) { 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 NettyHttpsServerConfig nettyHttpsServerConfig = (NettyHttpsServerConfig) nettyHttpServer.getNettyHttpServerConfig();
final ServerNameIndicationHandler serverNameIndicationHandler = final ServerNameIndicationHandler serverNameIndicationHandler =
new ServerNameIndicationHandler(nettyHttpsServerConfig, httpAddress, new ServerNameIndicationHandler(nettyHttpsServerConfig, httpAddress,

View file

@ -65,7 +65,7 @@ public class Https1Handler extends ChannelDuplexHandler {
} }
protected void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) { 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 { try {
HttpResponseBuilder serverResponseBuilder = HttpResponse.builder() HttpResponseBuilder serverResponseBuilder = HttpResponse.builder()
.setChannelHandlerContext(ctx); .setChannelHandlerContext(ctx);

View file

@ -45,7 +45,7 @@ public class Https2ChannelInitializer implements HttpChannelInitializer {
@Override @Override
public void init(Channel channel, NettyHttpServer nettyHttpServer, NettyCustomizer customizer) { 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 NettyHttpsServerConfig nettyHttpsServerConfig = (NettyHttpsServerConfig) nettyHttpServer.getNettyHttpServerConfig();
final ServerNameIndicationHandler serverNameIndicationHandler = final ServerNameIndicationHandler serverNameIndicationHandler =
new ServerNameIndicationHandler(nettyHttpsServerConfig, httpAddress, new ServerNameIndicationHandler(nettyHttpsServerConfig, httpAddress,

View file

@ -33,7 +33,7 @@ public class Https2ChildChannelInitializer extends ChannelInitializer<Channel> {
@Override @Override
protected void initChannel(Channel channel) { protected void initChannel(Channel channel) {
NettyHttpsServerConfig nettyHttpsServerConfig = (NettyHttpsServerConfig) server.getNettyHttpServerConfig(); 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); channel.attr(NettyHttpsServerConfig.ATTRIBUTE_KEY_SNI_HANDLER).set(serverNameIndicationHandler);
ChannelPipeline pipeline = channel.pipeline(); ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast("server-frame-converter", new Http2StreamFrameToHttpObjectCodec(true)); pipeline.addLast("server-frame-converter", new Http2StreamFrameToHttpObjectCodec(true));

View file

@ -37,7 +37,7 @@ public class Https2Handler extends ChannelDuplexHandler {
@Override @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof FullHttpRequest fullHttpRequest) { 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 { try {
Integer streamId = fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text()); Integer streamId = fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text());
HttpResponseBuilder httpsResponseBuilder = HttpResponse.builder() HttpResponseBuilder httpsResponseBuilder = HttpResponse.builder()

View file

@ -23,12 +23,6 @@ public class HttpRequest extends BaseHttpRequest {
return new HttpRequestBuilder(); return new HttpRequestBuilder();
} }
@Override
public InputStream getInputStream() {
//return new ByteBufInputStream(builder.fullHttpRequest.content());
return builder.byteBuffer != null ? new ByteBufferInputStream(builder.byteBuffer) : null;
}
@Override @Override
public ByteBuffer getBody() { public ByteBuffer getBody() {
return builder.getBody(); return builder.getBody();
@ -39,6 +33,11 @@ public class HttpRequest extends BaseHttpRequest {
return builder.getBodyAsChars(charset); return builder.getBodyAsChars(charset);
} }
@Override
public InputStream getInputStream() {
return builder.byteBuffer != null ? new ByteBufferInputStream(builder.byteBuffer) : null;
}
@Override @Override
public <R extends Request> R as(Class<R> type) { public <R extends Request> R as(Class<R> type) {
Objects.requireNonNull(type); Objects.requireNonNull(type);

View file

@ -17,12 +17,14 @@ import java.net.InetSocketAddress;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.CharBuffer; import java.nio.CharBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import org.xbib.net.http.server.Part; import org.xbib.net.http.server.Message;
public class HttpRequestBuilder extends BaseHttpRequestBuilder { public class HttpRequestBuilder extends BaseHttpRequestBuilder {
private static final Logger logger = Logger.getLogger(HttpRequestBuilder.class.getName()); private static final Logger logger = Logger.getLogger(HttpRequestBuilder.class.getName());
private FullHttpRequest httpRequest;
protected ByteBuffer byteBuffer; protected ByteBuffer byteBuffer;
protected HttpRequestBuilder() { protected HttpRequestBuilder() {
@ -39,13 +41,16 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder {
} }
public HttpRequestBuilder setFullHttpRequest(FullHttpRequest fullHttpRequest) { public HttpRequestBuilder setFullHttpRequest(FullHttpRequest fullHttpRequest) {
setVersion(HttpVersion.valueOf(fullHttpRequest.protocolVersion().text())); if (fullHttpRequest != null) {
setMethod(HttpMethod.valueOf(fullHttpRequest.method().name())); this.httpRequest = fullHttpRequest;
setRequestURI(fullHttpRequest.uri()); setVersion(HttpVersion.valueOf(fullHttpRequest.protocolVersion().text()));
fullHttpRequest.headers().entries().forEach(e -> addHeader(e.getKey(), e.getValue())); setMethod(HttpMethod.valueOf(fullHttpRequest.method().name()));
// read all bytes from request into a JDK ByteBuffer. This might be expensive. setRequestURI(fullHttpRequest.uri());
if (fullHttpRequest.content() != null) { fullHttpRequest.headers().entries().forEach(e -> addHeader(e.getKey(), e.getValue()));
byteBuffer = ByteBuffer.wrap(ByteBufUtil.getBytes(fullHttpRequest.content())); // read all bytes from request into a JDK ByteBuffer. This might be expensive.
if (fullHttpRequest.content() != null) {
byteBuffer = ByteBuffer.wrap(ByteBufUtil.getBytes(fullHttpRequest.content()));
}
} }
return this; return this;
} }
@ -110,12 +115,14 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder {
public HttpRequestBuilder addFileUpload(FileUpload fileUpload) throws IOException { public HttpRequestBuilder addFileUpload(FileUpload fileUpload) throws IOException {
logger.log(Level.FINE, "add file upload = " + fileUpload); logger.log(Level.FINE, "add file upload = " + fileUpload);
Part part = new Part(fileUpload.getContentType(), Message message = new Message(fileUpload.getContentType(),
fileUpload.getContentTransferEncoding(), fileUpload.getContentTransferEncoding(),
fileUpload.getFilename(), fileUpload.getFilename(),
fileUpload.isInMemory() ? null : fileUpload.getFile().toPath(), fileUpload.isInMemory() ? null : fileUpload.getFile().toPath(),
// can be expensive
ByteBuffer.wrap(fileUpload.get())); 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; return this;
} }
@ -130,6 +137,11 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder {
@Override @Override
public void release() { 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())); super.trailingHeaders.entries().forEach(e -> trailingHeaders.add(e.getKey(), e.getValue()));
HttpVersion httpVersion = HttpVersion.valueOf(version.text()); HttpVersion httpVersion = HttpVersion.valueOf(version.text());
FullHttpResponse fullHttpResponse = FullHttpResponse fullHttpResponse =
new DefaultFullHttpResponse(httpVersion, responseStatus, byteBuf.retain(), headers, trailingHeaders); new DefaultFullHttpResponse(httpVersion, responseStatus, byteBuf, headers, trailingHeaders);
ChannelFuture channelFuture; ChannelFuture channelFuture;
if (sequenceId != null) { if (sequenceId != null) {
HttpPipelinedResponse httpPipelinedResponse = new HttpPipelinedResponse(fullHttpResponse, 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.ChannelOption;
import io.netty.channel.EventLoopGroup; import io.netty.channel.EventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel; 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.LogLevel;
import io.netty.handler.logging.LoggingHandler; import io.netty.handler.logging.LoggingHandler;
import io.netty.util.AttributeKey;
import java.io.IOException; import java.io.IOException;
import java.net.BindException; import java.net.BindException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@ -18,6 +18,7 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.ServiceLoader; import java.util.ServiceLoader;
import java.util.Set; import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; 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.HttpServerContext;
import org.xbib.net.http.server.HttpServer; import org.xbib.net.http.server.HttpServer;
import org.xbib.net.http.server.domain.HttpDomain; import org.xbib.net.http.server.domain.HttpDomain;
import org.xbib.net.http.server.executor.CallableReleasable;
import org.xbib.net.http.server.route.HttpRouter; import org.xbib.net.http.server.route.HttpRouter;
/** /**
@ -94,27 +94,44 @@ public class NettyHttpServer implements HttpServer {
for (HttpAddress httpAddress : httpAddressSet) { for (HttpAddress httpAddress : httpAddressSet) {
SocketConfig socketConfig = httpAddress.getSocketConfig(); SocketConfig socketConfig = httpAddress.getSocketConfig();
ServerBootstrap bootstrap = new ServerBootstrap() ServerBootstrap bootstrap = new ServerBootstrap()
.group(parentEventLoopGroup, childEventLoopGroup) .group(parentEventLoopGroup, childEventLoopGroup)
.channel(socketChannelClass) .channel(socketChannelClass)
.option(ChannelOption.ALLOCATOR, builder.byteBufAllocator) .option(ChannelOption.ALLOCATOR, builder.byteBufAllocator)
.option(ChannelOption.SO_REUSEADDR, socketConfig.isReuseAddr()) .option(ChannelOption.SO_REUSEADDR, socketConfig.isReuseAddr())
.option(ChannelOption.SO_RCVBUF, socketConfig.getTcpReceiveBufferSize()) .option(ChannelOption.SO_RCVBUF, socketConfig.getTcpReceiveBufferSize())
.option(ChannelOption.SO_BACKLOG, socketConfig.getBackLogSize()) .option(ChannelOption.SO_BACKLOG, socketConfig.getBackLogSize())
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, socketConfig.getConnectTimeoutMillis()) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, socketConfig.getConnectTimeoutMillis())
.childOption(ChannelOption.ALLOCATOR, builder.byteBufAllocator) .childOption(ChannelOption.ALLOCATOR, builder.byteBufAllocator)
.childOption(ChannelOption.SO_REUSEADDR, socketConfig.isReuseAddr()) .childOption(ChannelOption.SO_REUSEADDR, socketConfig.isReuseAddr())
.childOption(ChannelOption.TCP_NODELAY, socketConfig.isTcpNodelay()) .childOption(ChannelOption.TCP_NODELAY, socketConfig.isTcpNodelay())
.childOption(ChannelOption.SO_SNDBUF, socketConfig.getTcpSendBufferSize()) .childOption(ChannelOption.SO_SNDBUF, socketConfig.getTcpSendBufferSize())
.childOption(ChannelOption.SO_RCVBUF, socketConfig.getTcpReceiveBufferSize()) .childOption(ChannelOption.SO_RCVBUF, socketConfig.getTcpReceiveBufferSize())
.childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, socketConfig.getConnectTimeoutMillis()) .childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, socketConfig.getConnectTimeoutMillis())
.childHandler(new ChannelInitializer<>() { .childHandler(new ChannelInitializer<>() {
@Override @Override
protected void initChannel(Channel ch) { protected void initChannel(Channel channel) {
AttributeKey<HttpAddress> key = AttributeKey.valueOf("_address"); channel.closeFuture().addListener((ChannelFuture future) -> {
ch.attr(key).set(httpAddress); Channel ch = future.channel();
createChannelInitializer(httpAddress).init(ch, getServer(), builder.nettyCustomizer); 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()) { if (getNettyHttpServerConfig().isDebug()) {
bootstrap.handler(new LoggingHandler("server-logging", LogLevel.DEBUG)); bootstrap.handler(new LoggingHandler("server-logging", LogLevel.DEBUG));
} }
@ -168,43 +185,25 @@ public class NettyHttpServer implements HttpServer {
@Override @Override
public void dispatch(HttpRequestBuilder requestBuilder, public void dispatch(HttpRequestBuilder requestBuilder,
HttpResponseBuilder responseBuilder) { HttpResponseBuilder responseBuilder) {
CallableReleasable<?> callableReleasable = new CallableReleasable<>() { Callable<?> callable = (Callable<Object>) () -> {
@Override HttpRouter router = builder.application.getRouter();
public Object call() { router.route(builder.application, requestBuilder, responseBuilder);
HttpRouter router = builder.application.getRouter(); return true;
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 @Override
public void dispatch(HttpRequestBuilder requestBuilder, public void dispatch(HttpRequestBuilder requestBuilder,
HttpResponseBuilder responseBuilder, HttpResponseBuilder responseBuilder,
HttpResponseStatus responseStatus) { HttpResponseStatus responseStatus) {
CallableReleasable<?> callableReleasable = new CallableReleasable<>() { Callable<?> callable = (Callable<Object>) () -> {
@Override HttpRouter router = builder.application.getRouter();
public Object call() { HttpServerContext httpServerContext = builder.application.createContext(null, requestBuilder, responseBuilder);
HttpRouter router = builder.application.getRouter(); router.routeStatus(responseStatus, httpServerContext);
HttpServerContext httpServerContext = builder.application.createContext(null, requestBuilder, responseBuilder); return true;
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 @Override

View file

@ -1,12 +1,19 @@
package org.xbib.net.http.server.netty; package org.xbib.net.http.server.netty;
import io.netty.handler.codec.http.multipart.HttpDataFactory;
import io.netty.util.AttributeKey; import io.netty.util.AttributeKey;
import org.xbib.net.http.HttpAddress; import org.xbib.net.http.HttpAddress;
import org.xbib.net.http.server.HttpServerConfig; import org.xbib.net.http.server.HttpServerConfig;
public class NettyHttpServerConfig extends 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. * 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, protected void requestReceived(ChannelHandlerContext ctx,
FullHttpRequest fullHttpRequest, FullHttpRequest fullHttpRequest,
Integer sequenceId) { Integer sequenceId) {
HttpAddress httpAddress = ctx.channel().attr(NettyHttpServerConfig.ATTRIBUTE_KEY_HTTP_ADDRESS).get(); HttpAddress httpAddress = ctx.channel().attr(NettyHttpServerConfig.ATTRIBUTE_HTTP_ADDRESS).get();
try { try {
HttpResponseBuilder serverResponseBuilder = HttpResponse.builder() HttpResponseBuilder httpResponseBuilder = HttpResponse.builder()
.setChannelHandlerContext(ctx); .setChannelHandlerContext(ctx);
if (nettyHttpServer.getNettyHttpServerConfig().isPipeliningEnabled()) { 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. // 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) .setFullHttpRequest(fullHttpRequest)
.setBaseURL(httpAddress, .setBaseURL(httpAddress,
fullHttpRequest.uri(), fullHttpRequest.uri(),
fullHttpRequest.headers().get(HttpHeaderNames.HOST)) fullHttpRequest.headers().get(HttpHeaderNames.HOST))
.setLocalAddress((InetSocketAddress) ctx.channel().localAddress()) .setLocalAddress(localAddress)
.setRemoteAddress((InetSocketAddress) ctx.channel().remoteAddress()) .setRemoteAddress(remoteAddress)
.setSequenceId(sequenceId); .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) { } catch (Exception e) {
logger.log(Level.SEVERE, "bad request: " + e.getMessage(), e); logger.log(Level.SEVERE, "bad request: " + e.getMessage(), e);
DefaultFullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, 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.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory; import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.FileUpload; import io.netty.handler.codec.http.multipart.FileUpload;
import io.netty.handler.codec.http.multipart.HttpDataFactory; import io.netty.handler.codec.http.multipart.HttpDataFactory;
@ -45,7 +44,9 @@ public class HttpFileUploadHandler extends SimpleChannelInboundHandler<HttpObjec
httpRequest = (HttpRequest) httpObject; httpRequest = (HttpRequest) httpObject;
// peek into request if we have a POST request // peek into request if we have a POST request
if (httpRequest.method() == HttpMethod.POST) { 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); httpDecoder = new HttpPostRequestDecoder(factory, httpRequest);
} }
} }
@ -68,12 +69,6 @@ public class HttpFileUploadHandler extends SimpleChannelInboundHandler<HttpObjec
} catch (HttpPostRequestDecoder.EndOfDataDecoderException e) { } catch (HttpPostRequestDecoder.EndOfDataDecoderException e) {
logger.log(Level.FINEST, "end of data decoder exception"); 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, protected void requestReceived(ChannelHandlerContext ctx,
HttpRequest httpRequest, HttpRequest httpRequest,
FileUpload fileUpload) { FileUpload fileUpload) {
HttpAddress httpAddress = ctx.channel().attr(NettyHttpServerConfig.ATTRIBUTE_KEY_HTTP_ADDRESS).get(); HttpAddress httpAddress = ctx.channel().attr(NettyHttpServerConfig.ATTRIBUTE_HTTP_ADDRESS).get();
try { try {
HttpResponseBuilder serverResponseBuilder = HttpResponse.builder() HttpResponseBuilder serverResponseBuilder = HttpResponse.builder()
.setChannelHandlerContext(ctx); .setChannelHandlerContext(ctx);

View file

@ -49,7 +49,7 @@ public class Http2ChannelInitializer implements HttpChannelInitializer {
@Override @Override
public void init(Channel channel, NettyHttpServer nettyHttpServer, NettyCustomizer customizer) { 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(); final NettyHttpServerConfig nettyHttpServerConfig = nettyHttpServer.getNettyHttpServerConfig();
ChannelPipeline pipeline = channel.pipeline(); ChannelPipeline pipeline = channel.pipeline();
if (nettyHttpServerConfig.isDebug()) { if (nettyHttpServerConfig.isDebug()) {

View file

@ -28,7 +28,7 @@ public class Http2ChildChannelInitializer extends ChannelInitializer<Channel> {
@Override @Override
protected void initChannel(Channel channel) { protected void initChannel(Channel channel) {
NettyHttpServerConfig nettyHttpServerConfig = nettyHttpServer.getNettyHttpServerConfig(); 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(); ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast("server-frame-converter", pipeline.addLast("server-frame-converter",
new Http2StreamFrameToHttpObjectCodec(true)); new Http2StreamFrameToHttpObjectCodec(true));

View file

@ -1,5 +1,6 @@
package org.xbib.net.http.server.netty.http2; package org.xbib.net.http.server.netty.http2;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler; 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.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.multipart.HttpDataFactory;
import io.netty.handler.codec.http2.HttpConversionUtil; import io.netty.handler.codec.http2.HttpConversionUtil;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@ -37,7 +39,7 @@ public class Http2Handler extends ChannelDuplexHandler {
@Override @Override
public void channelRead(ChannelHandlerContext ctx, Object object) throws IOException { public void channelRead(ChannelHandlerContext ctx, Object object) throws IOException {
if (object instanceof FullHttpRequest fullHttpRequest) { 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 { try {
Integer streamId = fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text()); Integer streamId = fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text());
HttpResponseBuilder httpResponseBuilder = HttpResponse.builder() HttpResponseBuilder httpResponseBuilder = HttpResponse.builder()
@ -47,23 +49,26 @@ public class Http2Handler extends ChannelDuplexHandler {
if (streamId != null) { if (streamId != null) {
httpResponseBuilder.setStreamId(streamId + 1); 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) .setFullHttpRequest(fullHttpRequest)
.setBaseURL(httpAddress, .setBaseURL(httpAddress,
fullHttpRequest.uri(), fullHttpRequest.uri(),
fullHttpRequest.headers().get(HttpHeaderNames.HOST)) fullHttpRequest.headers().get(HttpHeaderNames.HOST))
.setLocalAddress((InetSocketAddress) ctx.channel().localAddress()) .setLocalAddress(localAddress)
.setRemoteAddress((InetSocketAddress) ctx.channel().remoteAddress()) .setRemoteAddress(remoteAddress)
.setStreamId(streamId); .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) { } catch (Exception e) {
logger.log(Level.SEVERE, "bad request:" + e.getMessage(), e); logger.log(Level.SEVERE, "bad request:" + e.getMessage(), e);
DefaultFullHttpResponse fullHttpResponse = DefaultFullHttpResponse fullHttpResponse =
new DefaultFullHttpResponse(io.netty.handler.codec.http.HttpVersion.valueOf(httpAddress.getVersion().text()), new DefaultFullHttpResponse(io.netty.handler.codec.http.HttpVersion.valueOf(httpAddress.getVersion().text()),
HttpResponseStatus.BAD_REQUEST); HttpResponseStatus.BAD_REQUEST);
ctx.writeAndFlush(fullHttpResponse).addListener(ChannelFutureListener.CLOSE); 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); logger.log(Level.SEVERE, cause.getMessage(), cause);
ctx.close(); 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()); private static final Logger logger = Logger.getLogger(NettyHttp2ServerMultiRequestLoadTest.class.getName());
@Test @Test
public void testHttp2Load() throws Exception { public void testHttp2Multi() throws Exception {
int requests = 1024;
ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.PARANOID); ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.PARANOID);
URL url = URL.from("http://localhost:8008/domain"); URL url = URL.from("http://localhost:8008/domain");
HttpAddress httpAddress = HttpAddress.http2(url); HttpAddress httpAddress = HttpAddress.http2(url);
NettyHttpServerConfig serverConfig = new NettyHttpServerConfig(); NettyHttpServerConfig serverConfig = new NettyHttpServerConfig();
@ -74,7 +77,6 @@ public class NettyHttp2ServerMultiRequestLoadTest {
.build()) { .build()) {
server.bind(); server.bind();
NettyHttpClientConfig config = new NettyHttpClientConfig(); NettyHttpClientConfig config = new NettyHttpClientConfig();
int requests = 1024;
AtomicInteger count = new AtomicInteger(); AtomicInteger count = new AtomicInteger();
try (NettyHttpClient client = NettyHttpClient.builder() try (NettyHttpClient client = NettyHttpClient.builder()
.setConfig(config) .setConfig(config)

View file

@ -39,7 +39,7 @@ public class NettyHttp2ServerTest {
NettyHttpServerConfig nettyHttpServerConfig = new NettyHttpServerConfig(); NettyHttpServerConfig nettyHttpServerConfig = new NettyHttpServerConfig();
nettyHttpServerConfig.setServerName("NettyHttp2ClearTextServer", nettyHttpServerConfig.setServerName("NettyHttp2ClearTextServer",
Bootstrap.class.getPackage().getImplementationVersion()); Bootstrap.class.getPackage().getImplementationVersion());
nettyHttpServerConfig.setNetworkClass(NetworkClass.ANY); nettyHttpServerConfig.setNetworkClass(NetworkClass.LOCAL);
nettyHttpServerConfig.setDebug(true); nettyHttpServerConfig.setDebug(true);
HttpRouter router = BaseHttpRouter.builder() HttpRouter router = BaseHttpRouter.builder()
@ -52,10 +52,11 @@ public class NettyHttp2ServerTest {
.setResponseStatus(HttpResponseStatus.OK) .setResponseStatus(HttpResponseStatus.OK)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
.setCharset(StandardCharsets.UTF_8); .setCharset(StandardCharsets.UTF_8);
ctx.write("domain " + ctx.write("Hello, here is my response: " +
ctx.httpRequest().getParameter() + " " + ctx.httpRequest().getParameter() + " " +
ctx.httpRequest().getLocalAddress() + " " + ctx.httpRequest().getLocalAddress() + " " +
ctx.httpRequest().getRemoteAddress()); ctx.httpRequest().getRemoteAddress());
ctx.done();
}) })
.build()) .build())
.build()) .build())

View file

@ -55,8 +55,8 @@ public class NettyHttpServerBodyTest {
" local address = " + ctx.httpRequest().getLocalAddress() + " local address = " + ctx.httpRequest().getLocalAddress() +
" remote address = " + ctx.httpRequest().getRemoteAddress() + " remote address = " + ctx.httpRequest().getRemoteAddress() +
" attributes = " + ctx.getAttributes() + " attributes = " + ctx.getAttributes() +
" body = " + body " body = " + body);
); ctx.done();
}) })
.build()) .build())
.build()) .build())

View file

@ -1,12 +1,17 @@
package org.xbib.net.http.netty.test; package org.xbib.net.http.netty.test;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.xbib.net.NetworkClass; import org.xbib.net.NetworkClass;
import org.xbib.net.URL; 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.HttpHeaderValues;
import org.xbib.net.http.HttpMethod; import org.xbib.net.http.HttpMethod;
import org.xbib.net.http.HttpResponseStatus; 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.HttpRequest;
import org.xbib.net.http.client.netty.NettyHttpClient; import org.xbib.net.http.client.netty.NettyHttpClient;
import org.xbib.net.http.client.netty.NettyHttpClientConfig; 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()); private static final Logger logger = Logger.getLogger(NettyHttpServerFileUploadTest.class.getName());
@Test @Test
public void testFileUpload() throws Exception { public void testSimpleFileUpload() throws Exception {
URL url = URL.from("http://localhost:8008/domain/"); URL url = URL.from("http://localhost:8008/");
HttpAddress httpAddress1 = HttpAddress.http1(url); HttpAddress httpAddress1 = HttpAddress.http1(url);
NettyHttpServerConfig nettyHttpServerConfig = new NettyHttpServerConfig(); NettyHttpServerConfig nettyHttpServerConfig = new NettyHttpServerConfig();
nettyHttpServerConfig.setServerName("NettyHttpServer", nettyHttpServerConfig.setServerName("NettyHttpServer",
@ -50,11 +55,14 @@ public class NettyHttpServerFileUploadTest {
.addDomain(BaseHttpDomain.builder() .addDomain(BaseHttpDomain.builder()
.setHttpAddress(httpAddress1) .setHttpAddress(httpAddress1)
.addService(BaseHttpService.builder() .addService(BaseHttpService.builder()
.setPath("/domain") .setPath("/")
.setMethod(HttpMethod.POST) .setMethod(HttpMethod.POST)
.setHandler(ctx -> { .setHandler(ctx -> {
logger.log(Level.FINEST, "handler starting"); 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() ctx.response()
.setResponseStatus(HttpResponseStatus.OK) .setResponseStatus(HttpResponseStatus.OK)
.setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN) .setHeader(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
@ -63,7 +71,7 @@ public class NettyHttpServerFileUploadTest {
" local address = " + ctx.httpRequest().getLocalAddress() + " local address = " + ctx.httpRequest().getLocalAddress() +
" remote address = " + ctx.httpRequest().getRemoteAddress() + " remote address = " + ctx.httpRequest().getRemoteAddress() +
" attributes = " + ctx.getAttributes() + " attributes = " + ctx.getAttributes() +
" parts = " + parts " message = " + message
); );
}) })
.build()) .build())
@ -82,9 +90,10 @@ public class NettyHttpServerFileUploadTest {
.build()) { .build()) {
server.bind(); server.bind();
NettyHttpClientConfig config = new NettyHttpClientConfig() NettyHttpClientConfig config = new NettyHttpClientConfig()
.setGzipEnabled(false) .setGzipEnabled(true)
.setChunkWriteEnabled(true) .setChunkWriteEnabled(true)
.setObjectAggregationEnabled(true) .setObjectAggregationEnabled(true)
.setFileUploadEnabled(true)
.setDebug(true); .setDebug(true);
AtomicBoolean received = new AtomicBoolean(); AtomicBoolean received = new AtomicBoolean();
try (NettyHttpClient client = NettyHttpClient.builder() try (NettyHttpClient client = NettyHttpClient.builder()
@ -92,8 +101,96 @@ public class NettyHttpServerFileUploadTest {
.build()) { .build()) {
HttpRequest request = HttpRequest.post() HttpRequest request = HttpRequest.post()
.setURL(url) .setURL(url)
.addPart(new Part("text/plain", "base64", .addMessage(new Message("text/plain",
"test", Paths.get("build.gradle"), StandardCharsets.UTF_8)) "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 -> { .setResponseListener(resp -> {
logger.log(Level.INFO, "got response:" + logger.log(Level.INFO, "got response:" +
" status = " + resp.getStatus() + " status = " + resp.getStatus() +

View file

@ -33,13 +33,16 @@ public class NettyHttpServerMultiRequestLoadTest {
private static final Logger logger = Logger.getLogger(NettyHttpServerMultiRequestLoadTest.class.getName()); private static final Logger logger = Logger.getLogger(NettyHttpServerMultiRequestLoadTest.class.getName());
@Test @Test
public void loadTestHttp1() throws Exception { public void testHttp1Multi() throws Exception {
int requests = 1024;
ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.PARANOID); ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.PARANOID);
URL url = URL.from("http://localhost:8008/domain"); URL url = URL.from("http://localhost:8008/domain");
HttpAddress httpAddress = HttpAddress.http1(url); HttpAddress httpAddress = HttpAddress.http1(url);
NettyHttpServerConfig serverConfig = new NettyHttpServerConfig(); NettyHttpServerConfig serverConfig = new NettyHttpServerConfig();
serverConfig.setServerName("NettyHttpServer", Bootstrap.class.getPackage().getImplementationVersion()); serverConfig.setServerName("NettyHttpServer", Bootstrap.class.getPackage().getImplementationVersion());
serverConfig.setNetworkClass(NetworkClass.LOOPBACK); serverConfig.setNetworkClass(NetworkClass.LOCAL);
HttpRouter router = BaseHttpRouter.builder() HttpRouter router = BaseHttpRouter.builder()
.addDomain(BaseHttpDomain.builder() .addDomain(BaseHttpDomain.builder()
@ -57,6 +60,7 @@ public class NettyHttpServerMultiRequestLoadTest {
" attributes = " + ctx.getAttributes() + " attributes = " + ctx.getAttributes() +
" local address = " + ctx.httpRequest().getLocalAddress() + " local address = " + ctx.httpRequest().getLocalAddress() +
" remote address = " + ctx.httpRequest().getRemoteAddress()); " remote address = " + ctx.httpRequest().getRemoteAddress());
ctx.done();
}) })
.build()) .build())
.build()) .build())
@ -74,7 +78,6 @@ public class NettyHttpServerMultiRequestLoadTest {
.build()) { .build()) {
server.bind(); server.bind();
NettyHttpClientConfig config = new NettyHttpClientConfig(); NettyHttpClientConfig config = new NettyHttpClientConfig();
int requests = 1024;
AtomicInteger count = new AtomicInteger(); AtomicInteger count = new AtomicInteger();
try (NettyHttpClient client = NettyHttpClient.builder() try (NettyHttpClient client = NettyHttpClient.builder()
.setConfig(config) .setConfig(config)

View file

@ -40,7 +40,6 @@ public class NettyHttpServerTest {
Bootstrap.class.getPackage().getImplementationVersion()); Bootstrap.class.getPackage().getImplementationVersion());
nettyHttpServerConfig.setNetworkClass(NetworkClass.LOCAL); nettyHttpServerConfig.setNetworkClass(NetworkClass.LOCAL);
nettyHttpServerConfig.setDebug(true); nettyHttpServerConfig.setDebug(true);
nettyHttpServerConfig.setPipelining(false);
HttpRouter router = BaseHttpRouter.builder() HttpRouter router = BaseHttpRouter.builder()
.addDomain(BaseHttpDomain.builder() .addDomain(BaseHttpDomain.builder()
@ -56,8 +55,8 @@ public class NettyHttpServerTest {
" parameter = " + ctx.httpRequest().getParameter().allToString() + " parameter = " + ctx.httpRequest().getParameter().allToString() +
" local address = " + ctx.httpRequest().getLocalAddress() + " local address = " + ctx.httpRequest().getLocalAddress() +
" remote address = " + ctx.httpRequest().getRemoteAddress() + " remote address = " + ctx.httpRequest().getRemoteAddress() +
" attributes = " + ctx.getAttributes() " attributes = " + ctx.getAttributes());
); ctx.done();
}) })
.build()) .build())
.build()) .build())

View file

@ -1,17 +1,8 @@
package org.xbib.net.http.server.nio; package org.xbib.net.http.server.nio;
import org.xbib.net.buffer.DataBuffer;
import org.xbib.net.http.server.BaseHttpResponse; import org.xbib.net.http.server.BaseHttpResponse;
import java.io.IOException; 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 { public class HttpResponse extends BaseHttpResponse {
@ -30,5 +21,4 @@ public class HttpResponse extends BaseHttpResponse {
public static HttpResponseBuilder builder() { public static HttpResponseBuilder builder() {
return new HttpResponseBuilder(); return new HttpResponseBuilder();
} }
} }

View file

@ -48,6 +48,8 @@ public class HttpResponseBuilder extends BaseHttpResponseBuilder {
internalWrite(fileChannel, bufferSize); internalWrite(fileChannel, bufferSize);
} else if (inputStream != null) { } else if (inputStream != null) {
internalWrite(inputStream, bufferSize); internalWrite(inputStream, bufferSize);
} else {
internalFlush();
} }
} catch (IOException e) { } catch (IOException e) {
logger.log(Level.SEVERE, e.getMessage(), e); logger.log(Level.SEVERE, e.getMessage(), e);

View file

@ -1,6 +1,7 @@
package org.xbib.net.http.server.nio; package org.xbib.net.http.server.nio;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.Callable;
import org.xbib.net.http.HttpAddress; import org.xbib.net.http.HttpAddress;
import org.xbib.net.http.HttpHeaderNames; import org.xbib.net.http.HttpHeaderNames;
import org.xbib.net.http.HttpHeaders; import org.xbib.net.http.HttpHeaders;
@ -33,7 +34,6 @@ import java.util.logging.Logger;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.xbib.net.http.server.domain.HttpDomain; import org.xbib.net.http.server.domain.HttpDomain;
import org.xbib.net.http.server.executor.CallableReleasable;
import org.xbib.net.http.server.route.HttpRouter; import org.xbib.net.http.server.route.HttpRouter;
public class NioHttpServer implements HttpServer { public class NioHttpServer implements HttpServer {
@ -135,21 +135,12 @@ public class NioHttpServer implements HttpServer {
@Override @Override
public void dispatch(org.xbib.net.http.server.HttpRequestBuilder requestBuilder, public void dispatch(org.xbib.net.http.server.HttpRequestBuilder requestBuilder,
org.xbib.net.http.server.HttpResponseBuilder responseBuilder) { org.xbib.net.http.server.HttpResponseBuilder responseBuilder) {
CallableReleasable<?> callableReleasable = new CallableReleasable<>() { Callable<?> callable = (Callable<Object>) () -> {
@Override HttpRouter router = builder.application.getRouter();
public Object call() { router.route(builder.application, requestBuilder, responseBuilder);
HttpRouter router = builder.application.getRouter(); return true;
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 @Override
@ -157,21 +148,12 @@ public class NioHttpServer implements HttpServer {
org.xbib.net.http.server.HttpResponseBuilder responseBuilder, org.xbib.net.http.server.HttpResponseBuilder responseBuilder,
HttpResponseStatus responseStatus) { HttpResponseStatus responseStatus) {
HttpServerContext httpServerContext = builder.application.createContext(null, requestBuilder, responseBuilder); HttpServerContext httpServerContext = builder.application.createContext(null, requestBuilder, responseBuilder);
CallableReleasable<?> callableReleasable = new CallableReleasable<>() { Callable<?> callable = (Callable<Object>) () -> {
@Override HttpRouter router = builder.application.getRouter();
public Object call() { router.routeStatus(responseStatus, httpServerContext);
HttpRouter router = builder.application.getRouter(); return true;
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 @Override

View file

@ -1,6 +1,7 @@
package org.xbib.net.http.server.simple; package org.xbib.net.http.server.simple;
import java.util.Collection; import java.util.Collection;
import java.util.concurrent.Callable;
import org.xbib.net.NetworkClass; import org.xbib.net.NetworkClass;
import org.xbib.net.NetworkUtils; import org.xbib.net.NetworkUtils;
import org.xbib.net.SocketConfig; import org.xbib.net.SocketConfig;
@ -34,7 +35,6 @@ import java.util.logging.Logger;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.xbib.net.http.server.domain.HttpDomain; import org.xbib.net.http.server.domain.HttpDomain;
import org.xbib.net.http.server.executor.CallableReleasable;
import org.xbib.net.http.server.route.HttpRouter; import org.xbib.net.http.server.route.HttpRouter;
public class SimpleHttpServer implements HttpServer { public class SimpleHttpServer implements HttpServer {
@ -137,19 +137,10 @@ public class SimpleHttpServer implements HttpServer {
@Override @Override
public void dispatch(org.xbib.net.http.server.HttpRequestBuilder requestBuilder, public void dispatch(org.xbib.net.http.server.HttpRequestBuilder requestBuilder,
org.xbib.net.http.server.HttpResponseBuilder responseBuilder) { org.xbib.net.http.server.HttpResponseBuilder responseBuilder) {
CallableReleasable<?> callableReleasable = new CallableReleasable<>() { Callable<?> callableReleasable = (Callable<Object>) () -> {
@Override HttpRouter router = builder.application.getRouter();
public Object call() { router.route(builder.application, requestBuilder, responseBuilder);
HttpRouter router = builder.application.getRouter(); return true;
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(callableReleasable);
} }
@ -159,21 +150,12 @@ public class SimpleHttpServer implements HttpServer {
org.xbib.net.http.server.HttpResponseBuilder responseBuilder, org.xbib.net.http.server.HttpResponseBuilder responseBuilder,
HttpResponseStatus responseStatus) { HttpResponseStatus responseStatus) {
HttpServerContext httpServerContext = builder.application.createContext(null, requestBuilder, responseBuilder); HttpServerContext httpServerContext = builder.application.createContext(null, requestBuilder, responseBuilder);
CallableReleasable<?> callableReleasable = new CallableReleasable<>() { Callable<?> callable = (Callable<Object>) () -> {
@Override HttpRouter router = builder.application.getRouter();
public Object call() { router.routeStatus(responseStatus, httpServerContext);
HttpRouter router = builder.application.getRouter(); return true;
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 @Override

View file

@ -8,6 +8,7 @@ import org.xbib.net.http.HttpHeaderValues;
import org.xbib.net.http.HttpMethod; import org.xbib.net.http.HttpMethod;
import org.xbib.net.http.HttpResponseStatus; import org.xbib.net.http.HttpResponseStatus;
import org.xbib.net.http.HttpVersion; 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.application.BaseApplication;
import org.xbib.net.http.server.domain.BaseHttpDomain; import org.xbib.net.http.server.domain.BaseHttpDomain;
import org.xbib.net.http.server.route.BaseHttpRouter; import org.xbib.net.http.server.route.BaseHttpRouter;
@ -30,6 +31,7 @@ public class HttpRouterTest {
public void routerTest() throws Exception { public void routerTest() throws Exception {
URL baseURL = URL.http().host("localhost").port(8008).build(); URL baseURL = URL.http().host("localhost").port(8008).build();
HttpAddress httpAddress = HttpAddress.of(baseURL); HttpAddress httpAddress = HttpAddress.of(baseURL);
BaseHttpRouter router = BaseHttpRouter.builder() BaseHttpRouter router = BaseHttpRouter.builder()
.addDomain(BaseHttpDomain.builder() .addDomain(BaseHttpDomain.builder()
.setHttpAddress(httpAddress) .setHttpAddress(httpAddress)
@ -47,16 +49,25 @@ public class HttpRouterTest {
.build()) .build())
.build()) .build())
.build(); .build();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
HttpResponseBuilder httpResponse = HttpResponse.builder() HttpResponseBuilder httpResponse = HttpResponse.builder()
.setOutputStream(outputStream); .setOutputStream(outputStream);
HttpRequestBuilder httpRequest = HttpRequest.builder() HttpRequestBuilder httpRequest = HttpRequest.builder()
.setBaseURL(baseURL) .setBaseURL(baseURL)
.setVersion(HttpVersion.HTTP_1_1) .setVersion(HttpVersion.HTTP_1_1)
.setMethod(HttpMethod.DELETE) .setMethod(HttpMethod.DELETE)
.setRequestURI("/demo") .setRequestURI("/demo")
.addHeader(HttpHeaderNames.HOST, httpAddress.hostAndPort()); .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); String string = outputStream.toString(StandardCharsets.UTF_8);
Logger.getAnonymousLogger().log(Level.INFO, "the response string is = " + string); Logger.getAnonymousLogger().log(Level.INFO, "the response string is = " + string);
assertTrue(string.contains("/demo")); assertTrue(string.contains("/demo"));

View file

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

View file

@ -51,11 +51,11 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder {
protected boolean done; protected boolean done;
protected List<Part> parts; protected List<Message> messages;
protected BaseHttpRequestBuilder() { protected BaseHttpRequestBuilder() {
this.httpHeaders = new HttpHeaders(); this.httpHeaders = new HttpHeaders();
this.parts = new ArrayList<>(); this.messages = new ArrayList<>();
} }
@Override @Override
@ -269,11 +269,11 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder {
return this; return this;
} }
public BaseHttpRequestBuilder addPart(Part part) { public BaseHttpRequestBuilder addPart(Message message) {
if (done) { if (done) {
return this; return this;
} }
this.parts.add(part); this.messages.add(message);
return this; return this;
} }
@ -282,6 +282,11 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder {
this.done = true; this.done = true;
} }
@Override
public void release() {
// do nothing for now
}
private static String stripPort(String hostMaybePort) { private static String stripPort(String hostMaybePort) {
if (hostMaybePort == null) { if (hostMaybePort == null) {
return null; return null;
@ -297,5 +302,4 @@ public abstract class BaseHttpRequestBuilder implements HttpRequestBuilder {
int i = hostMaybePort.lastIndexOf(':'); int i = hostMaybePort.lastIndexOf(':');
return i >= 0 ? hostMaybePort.substring(i + 1) : null; return i >= 0 ? hostMaybePort.substring(i + 1) : null;
} }
} }

View file

@ -7,4 +7,9 @@ public abstract class BaseHttpResponse implements HttpResponse {
protected BaseHttpResponse(BaseHttpResponseBuilder builder) { protected BaseHttpResponse(BaseHttpResponseBuilder builder) {
this.builder = 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.io.InputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.util.List; import java.util.List;
import org.xbib.net.Attributes; import org.xbib.net.Attributes;
import org.xbib.net.Parameter; import org.xbib.net.Parameter;
@ -40,7 +39,7 @@ public interface HttpRequest extends Request {
InputStream getInputStream(); InputStream getInputStream();
List<Part> getParts(); List<Message> getMessages();
Attributes getAttributes(); Attributes getAttributes();
} }

View file

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

View file

@ -4,4 +4,5 @@ import org.xbib.net.Response;
public interface HttpResponse extends 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.ByteBuffer;
import java.nio.file.Path; import java.nio.file.Path;
import static java.nio.charset.StandardCharsets.UTF_8;
public class Part { public class Message {
private final String contentType; private final String contentType;
@ -16,11 +15,11 @@ public class Part {
private final ByteBuffer byteBuffer; private final ByteBuffer byteBuffer;
public Part(String contentType, public Message(String contentType,
String contentTransferEncoding, String contentTransferEncoding,
String name, String name,
Path path, Path path,
ByteBuffer byteBuffer) { ByteBuffer byteBuffer) {
this.contentType = contentType; this.contentType = contentType;
this.contentTransferEncoding = contentTransferEncoding; this.contentTransferEncoding = contentTransferEncoding;
this.name = name; this.name = name;
@ -50,6 +49,6 @@ public class Part {
@Override @Override
public String toString() { 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 @Override
public Application build() { public Application build() {
Objects.requireNonNull(httpRouter); Objects.requireNonNull(httpRouter, "http router must not be null");
return new BaseApplication(this); return new BaseApplication(this);
} }
} }

View file

@ -2,6 +2,7 @@ package org.xbib.net.http.server.executor;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -20,8 +21,8 @@ public class BaseExecutor implements Executor {
} }
@Override @Override
public void execute(CallableReleasable<?> callableReleasable) { public void execute(Callable<?> callable) {
builder.executor.submit(callableReleasable); builder.executor.submit(callable);
} }
@Override @Override

View file

@ -45,10 +45,5 @@ public class BaseThreadPoolExecutor extends ThreadPoolExecutor {
logger.log(Level.SEVERE, terminationCause.getMessage(), terminationCause); logger.log(Level.SEVERE, terminationCause.getMessage(), terminationCause);
return; 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; package org.xbib.net.http.server.executor;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.Callable;
public interface Executor { public interface Executor {
/** void execute(Callable<?> callable);
* Execute a task that must be released after execution.
*/
void execute(CallableReleasable<?> callableReleasable);
void shutdown() throws IOException; void shutdown() throws IOException;
} }