implement file uploading, release resource in channel close handler, clean up request/response API
This commit is contained in:
parent
dab29dbf9f
commit
98b13c8dc7
54 changed files with 993 additions and 1176 deletions
|
@ -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>
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]]>
|
]]>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,21 +105,23 @@ 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
|
||||||
|
if (nettyHttpClient.getClientConfig().isFileUploadEnabled()) {
|
||||||
HttpDataFactory httpDataFactory = new DefaultHttpDataFactory();
|
HttpDataFactory httpDataFactory = new DefaultHttpDataFactory();
|
||||||
|
channel.attr(NettyHttpClientConfig.ATTRIBUTE_HTTP_DATAFACTORY).set(httpDataFactory);
|
||||||
HttpPostRequestEncoder httpPostRequestEncoder = null;
|
HttpPostRequestEncoder httpPostRequestEncoder = null;
|
||||||
try {
|
try {
|
||||||
if (!request.getParts().isEmpty()) {
|
if (!request.getMessages().isEmpty()) {
|
||||||
httpPostRequestEncoder = new HttpPostRequestEncoder(httpDataFactory,
|
httpPostRequestEncoder = new HttpPostRequestEncoder(httpDataFactory,
|
||||||
fullHttpRequest,
|
fullHttpRequest,
|
||||||
true,
|
true,
|
||||||
StandardCharsets.UTF_8,
|
StandardCharsets.UTF_8,
|
||||||
HttpPostRequestEncoder.EncoderMode.RFC1738);
|
HttpPostRequestEncoder.EncoderMode.RFC1738);
|
||||||
for (Part part : request.getParts()) {
|
for (Message message : request.getMessages()) {
|
||||||
Path path = part.getPath();
|
Path path = message.getPath();
|
||||||
if (Files.exists(path)) {
|
if (Files.exists(path)) {
|
||||||
FileUpload fileUpload = httpDataFactory.createFileUpload(fullHttpRequest, part.getName(),
|
FileUpload fileUpload = httpDataFactory.createFileUpload(fullHttpRequest, message.getName(),
|
||||||
path.toFile().getName(), part.getContentType(), part.getContentTransferEncoding(),
|
path.toFile().getName(), message.getContentType(), message.getContentTransferEncoding(),
|
||||||
part.getCharset(), Files.size(path));
|
message.getCharset(), Files.size(path));
|
||||||
fileUpload.setContent(path.toFile());
|
fileUpload.setContent(path.toFile());
|
||||||
logger.log(Level.FINEST, "HTTP FORM file upload = " + fileUpload);
|
logger.log(Level.FINEST, "HTTP FORM file upload = " + fileUpload);
|
||||||
httpPostRequestEncoder.addBodyHttpData(fileUpload);
|
httpPostRequestEncoder.addBodyHttpData(fileUpload);
|
||||||
|
@ -137,8 +139,9 @@ public class Http1Interaction extends BaseInteraction {
|
||||||
channel.flush();
|
channel.flush();
|
||||||
} catch (HttpPostRequestEncoder.ErrorDataEncoderException e) {
|
} catch (HttpPostRequestEncoder.ErrorDataEncoderException e) {
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
} finally {
|
}
|
||||||
channel.attr(NettyHttpClientConfig.ATTRIBUTE_HTTP_DATAFACTORY).set(httpDataFactory);
|
} else {
|
||||||
|
channel.write(fullHttpRequest);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 +
|
||||||
|
"]";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,7 +16,7 @@ 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,
|
||||||
|
@ -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 + "]";
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,6 +41,8 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpRequestBuilder setFullHttpRequest(FullHttpRequest fullHttpRequest) {
|
public HttpRequestBuilder setFullHttpRequest(FullHttpRequest fullHttpRequest) {
|
||||||
|
if (fullHttpRequest != null) {
|
||||||
|
this.httpRequest = fullHttpRequest;
|
||||||
setVersion(HttpVersion.valueOf(fullHttpRequest.protocolVersion().text()));
|
setVersion(HttpVersion.valueOf(fullHttpRequest.protocolVersion().text()));
|
||||||
setMethod(HttpMethod.valueOf(fullHttpRequest.method().name()));
|
setMethod(HttpMethod.valueOf(fullHttpRequest.method().name()));
|
||||||
setRequestURI(fullHttpRequest.uri());
|
setRequestURI(fullHttpRequest.uri());
|
||||||
|
@ -47,6 +51,7 @@ public class HttpRequestBuilder extends BaseHttpRequestBuilder {
|
||||||
if (fullHttpRequest.content() != null) {
|
if (fullHttpRequest.content() != null) {
|
||||||
byteBuffer = ByteBuffer.wrap(ByteBufUtil.getBytes(fullHttpRequest.content()));
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -109,10 +109,27 @@ public class NettyHttpServer implements HttpServer {
|
||||||
.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()) {
|
||||||
|
@ -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
|
|
||||||
public Object call() {
|
|
||||||
HttpRouter router = builder.application.getRouter();
|
HttpRouter router = builder.application.getRouter();
|
||||||
router.route(builder.application, requestBuilder, responseBuilder);
|
router.route(builder.application, requestBuilder, responseBuilder);
|
||||||
return true;
|
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
|
|
||||||
public Object call() {
|
|
||||||
HttpRouter router = builder.application.getRouter();
|
HttpRouter router = builder.application.getRouter();
|
||||||
HttpServerContext httpServerContext = builder.application.createContext(null, requestBuilder, responseBuilder);
|
HttpServerContext httpServerContext = builder.application.createContext(null, requestBuilder, responseBuilder);
|
||||||
router.routeStatus(responseStatus, httpServerContext);
|
router.routeStatus(responseStatus, httpServerContext);
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void release() {
|
|
||||||
requestBuilder.release();
|
|
||||||
responseBuilder.release();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
builder.application.getExecutor().execute(callableReleasable);
|
builder.application.getExecutor().execute(callable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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() +
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
|
||||||
public Object call() {
|
|
||||||
HttpRouter router = builder.application.getRouter();
|
HttpRouter router = builder.application.getRouter();
|
||||||
router.route(builder.application, requestBuilder, responseBuilder);
|
router.route(builder.application, requestBuilder, responseBuilder);
|
||||||
return true;
|
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
|
|
||||||
public Object call() {
|
|
||||||
HttpRouter router = builder.application.getRouter();
|
HttpRouter router = builder.application.getRouter();
|
||||||
router.routeStatus(responseStatus, httpServerContext);
|
router.routeStatus(responseStatus, httpServerContext);
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void release() {
|
|
||||||
requestBuilder.release();
|
|
||||||
responseBuilder.release();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
builder.application.getExecutor().execute(callableReleasable);
|
builder.application.getExecutor().execute(callable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -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
|
|
||||||
public Object call() {
|
|
||||||
HttpRouter router = builder.application.getRouter();
|
HttpRouter router = builder.application.getRouter();
|
||||||
router.route(builder.application, requestBuilder, responseBuilder);
|
router.route(builder.application, requestBuilder, responseBuilder);
|
||||||
return true;
|
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
|
|
||||||
public Object call() {
|
|
||||||
HttpRouter router = builder.application.getRouter();
|
HttpRouter router = builder.application.getRouter();
|
||||||
router.routeStatus(responseStatus, httpServerContext);
|
router.routeStatus(responseStatus, httpServerContext);
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void release() {
|
|
||||||
requestBuilder.release();
|
|
||||||
responseBuilder.release();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
builder.application.getExecutor().execute(callableReleasable);
|
builder.application.getExecutor().execute(callable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -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"));
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -4,4 +4,5 @@ import org.xbib.net.Response;
|
||||||
|
|
||||||
public interface HttpResponse extends Response {
|
public interface HttpResponse extends Response {
|
||||||
|
|
||||||
|
void release();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,7 +15,7 @@ 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,
|
||||||
|
@ -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 + "]";
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
|
||||||
}
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue