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