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")
|
||||
// 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 {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
group = org.xbib
|
||||
name = netty-http
|
||||
version = 4.1.36.2
|
||||
version = 4.1.36.3
|
||||
|
||||
# main packages
|
||||
netty.version = 4.1.36.Final
|
||||
|
@ -8,7 +8,7 @@ tcnative.version = 2.0.25.Final
|
|||
alpnagent.version = 2.0.9
|
||||
|
||||
# common
|
||||
xbib-net-url.version = 1.3.1
|
||||
xbib-net-url.version = 1.3.2
|
||||
|
||||
# server
|
||||
bouncycastle.version = 1.61
|
||||
|
|
|
@ -153,8 +153,8 @@ public final class Client {
|
|||
}
|
||||
}
|
||||
|
||||
public static ClientBuilder builder() {
|
||||
return new ClientBuilder();
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public ClientConfig getClientConfig() {
|
||||
|
@ -292,12 +292,8 @@ public final class Client {
|
|||
close(transport);
|
||||
}
|
||||
|
||||
public void close(Transport transport) throws IOException {
|
||||
transport.close();
|
||||
transports.remove(transport);
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
public void shutdownGracefully() throws IOException {
|
||||
logger.log(Level.FINE, "shutting down gracefully");
|
||||
for (Transport transport : transports) {
|
||||
close(transport);
|
||||
}
|
||||
|
@ -305,14 +301,7 @@ public final class Client {
|
|||
if (hasPooledConnections()) {
|
||||
pool.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdownGracefully() throws IOException {
|
||||
close();
|
||||
shutdown();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
logger.log(Level.FINE, "shutting down");
|
||||
eventLoopGroup.shutdownGracefully();
|
||||
try {
|
||||
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.
|
||||
* @param clientConfig the client config
|
||||
|
@ -410,7 +404,7 @@ public final class Client {
|
|||
}
|
||||
}
|
||||
|
||||
class ClientChannelPoolHandler implements ChannelPoolHandler {
|
||||
private class ClientChannelPoolHandler implements ChannelPoolHandler {
|
||||
|
||||
@Override
|
||||
public void channelReleased(Channel channel) {
|
||||
|
@ -455,7 +449,7 @@ public final class Client {
|
|||
}
|
||||
}
|
||||
|
||||
public static class ClientBuilder {
|
||||
public static class Builder {
|
||||
|
||||
private ByteBufAllocator byteBufAllocator;
|
||||
|
||||
|
@ -465,16 +459,16 @@ public final class Client {
|
|||
|
||||
private ClientConfig clientConfig;
|
||||
|
||||
private ClientBuilder() {
|
||||
private Builder() {
|
||||
this.clientConfig = new ClientConfig();
|
||||
}
|
||||
|
||||
public ClientBuilder enableDebug() {
|
||||
public Builder enableDebug() {
|
||||
clientConfig.enableDebug();
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder disableDebug() {
|
||||
public Builder disableDebug() {
|
||||
clientConfig.disableDebug();
|
||||
return this;
|
||||
}
|
||||
|
@ -484,187 +478,187 @@ public final class Client {
|
|||
* @param byteBufAllocator the byte buf allocator
|
||||
* @return this builder
|
||||
*/
|
||||
public ClientBuilder setByteBufAllocator(ByteBufAllocator byteBufAllocator) {
|
||||
public Builder setByteBufAllocator(ByteBufAllocator byteBufAllocator) {
|
||||
this.byteBufAllocator = byteBufAllocator;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setEventLoop(EventLoopGroup eventLoopGroup) {
|
||||
public Builder setEventLoop(EventLoopGroup eventLoopGroup) {
|
||||
this.eventLoopGroup = eventLoopGroup;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setChannelClass(Class<SocketChannel> socketChannelClass) {
|
||||
public Builder setChannelClass(Class<SocketChannel> socketChannelClass) {
|
||||
this.socketChannelClass = socketChannelClass;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setThreadCount(int threadCount) {
|
||||
public Builder setThreadCount(int threadCount) {
|
||||
clientConfig.setThreadCount(threadCount);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setConnectTimeoutMillis(int connectTimeoutMillis) {
|
||||
public Builder setConnectTimeoutMillis(int connectTimeoutMillis) {
|
||||
clientConfig.setConnectTimeoutMillis(connectTimeoutMillis);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setTcpSendBufferSize(int tcpSendBufferSize) {
|
||||
public Builder setTcpSendBufferSize(int tcpSendBufferSize) {
|
||||
clientConfig.setTcpSendBufferSize(tcpSendBufferSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setTcpReceiveBufferSize(int tcpReceiveBufferSize) {
|
||||
public Builder setTcpReceiveBufferSize(int tcpReceiveBufferSize) {
|
||||
clientConfig.setTcpReceiveBufferSize(tcpReceiveBufferSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setTcpNodelay(boolean tcpNodelay) {
|
||||
public Builder setTcpNodelay(boolean tcpNodelay) {
|
||||
clientConfig.setTcpNodelay(tcpNodelay);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setKeepAlive(boolean keepAlive) {
|
||||
public Builder setKeepAlive(boolean keepAlive) {
|
||||
clientConfig.setKeepAlive(keepAlive);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setReuseAddr(boolean reuseAddr) {
|
||||
public Builder setReuseAddr(boolean reuseAddr) {
|
||||
clientConfig.setReuseAddr(reuseAddr);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setMaxChunkSize(int maxChunkSize) {
|
||||
public Builder setMaxChunkSize(int maxChunkSize) {
|
||||
clientConfig.setMaxChunkSize(maxChunkSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setMaxInitialLineLength(int maxInitialLineLength) {
|
||||
public Builder setMaxInitialLineLength(int maxInitialLineLength) {
|
||||
clientConfig.setMaxInitialLineLength(maxInitialLineLength);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setMaxHeadersSize(int maxHeadersSize) {
|
||||
public Builder setMaxHeadersSize(int maxHeadersSize) {
|
||||
clientConfig.setMaxHeadersSize(maxHeadersSize);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setMaxContentLength(int maxContentLength) {
|
||||
public Builder setMaxContentLength(int maxContentLength) {
|
||||
clientConfig.setMaxContentLength(maxContentLength);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setMaxCompositeBufferComponents(int maxCompositeBufferComponents) {
|
||||
public Builder setMaxCompositeBufferComponents(int maxCompositeBufferComponents) {
|
||||
clientConfig.setMaxCompositeBufferComponents(maxCompositeBufferComponents);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setReadTimeoutMillis(int readTimeoutMillis) {
|
||||
public Builder setReadTimeoutMillis(int readTimeoutMillis) {
|
||||
clientConfig.setReadTimeoutMillis(readTimeoutMillis);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setEnableGzip(boolean enableGzip) {
|
||||
public Builder setEnableGzip(boolean enableGzip) {
|
||||
clientConfig.setEnableGzip(enableGzip);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setSslProvider(SslProvider sslProvider) {
|
||||
public Builder setSslProvider(SslProvider sslProvider) {
|
||||
clientConfig.setSslProvider(sslProvider);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setJdkSslProvider() {
|
||||
public Builder setJdkSslProvider() {
|
||||
clientConfig.setJdkSslProvider();
|
||||
clientConfig.setCiphers(SecurityUtil.Defaults.JDK_CIPHERS);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setOpenSSLSslProvider() {
|
||||
public Builder setOpenSSLSslProvider() {
|
||||
clientConfig.setOpenSSLSslProvider();
|
||||
clientConfig.setCiphers(SecurityUtil.Defaults.OPENSSL_CIPHERS);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setSslContextProvider(Provider provider) {
|
||||
public Builder setSslContextProvider(Provider provider) {
|
||||
clientConfig.setSslContextProvider(provider);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setCiphers(Iterable<String> ciphers) {
|
||||
public Builder setCiphers(Iterable<String> ciphers) {
|
||||
clientConfig.setCiphers(ciphers);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setCipherSuiteFilter(CipherSuiteFilter cipherSuiteFilter) {
|
||||
public Builder setCipherSuiteFilter(CipherSuiteFilter cipherSuiteFilter) {
|
||||
clientConfig.setCipherSuiteFilter(cipherSuiteFilter);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream) {
|
||||
public Builder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream) {
|
||||
clientConfig.setKeyCert(keyCertChainInputStream, keyInputStream);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream,
|
||||
String keyPassword) {
|
||||
public Builder setKeyCert(InputStream keyCertChainInputStream, InputStream keyInputStream,
|
||||
String keyPassword) {
|
||||
clientConfig.setKeyCert(keyCertChainInputStream, keyInputStream, keyPassword);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setTrustManagerFactory(TrustManagerFactory trustManagerFactory) {
|
||||
public Builder setTrustManagerFactory(TrustManagerFactory trustManagerFactory) {
|
||||
clientConfig.setTrustManagerFactory(trustManagerFactory);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder trustInsecure() {
|
||||
public Builder trustInsecure() {
|
||||
clientConfig.setTrustManagerFactory(InsecureTrustManagerFactory.INSTANCE);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setClientAuthMode(ClientAuthMode clientAuthMode) {
|
||||
public Builder setClientAuthMode(ClientAuthMode clientAuthMode) {
|
||||
clientConfig.setClientAuthMode(clientAuthMode);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setHttpProxyHandler(HttpProxyHandler httpProxyHandler) {
|
||||
public Builder setHttpProxyHandler(HttpProxyHandler httpProxyHandler) {
|
||||
clientConfig.setHttpProxyHandler(httpProxyHandler);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder addPoolNode(HttpAddress httpAddress) {
|
||||
public Builder addPoolNode(HttpAddress httpAddress) {
|
||||
clientConfig.addPoolNode(httpAddress);
|
||||
clientConfig.setPoolVersion(httpAddress.getVersion());
|
||||
clientConfig.setPoolSecure(httpAddress.isSecure());
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setPoolNodeConnectionLimit(int nodeConnectionLimit) {
|
||||
public Builder setPoolNodeConnectionLimit(int nodeConnectionLimit) {
|
||||
clientConfig.setPoolNodeConnectionLimit(nodeConnectionLimit);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setRetriesPerPoolNode(int retriesPerNode) {
|
||||
public Builder setRetriesPerPoolNode(int retriesPerNode) {
|
||||
clientConfig.setRetriesPerPoolNode(retriesPerNode);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder addServerNameForIdentification(String serverName) {
|
||||
public Builder addServerNameForIdentification(String serverName) {
|
||||
clientConfig.addServerNameForIdentification(serverName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setHttp2Settings(Http2Settings http2Settings) {
|
||||
public Builder setHttp2Settings(Http2Settings http2Settings) {
|
||||
clientConfig.setHttp2Settings(http2Settings);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark) {
|
||||
public Builder setWriteBufferWaterMark(WriteBufferWaterMark writeBufferWaterMark) {
|
||||
clientConfig.setWriteBufferWaterMark(writeBufferWaterMark);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientBuilder enableNegotiation(boolean enableNegotiation) {
|
||||
public Builder enableNegotiation(boolean enableNegotiation) {
|
||||
clientConfig.setEnableNegotiation(enableNegotiation);
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -2,20 +2,42 @@ package org.xbib.netty.http.client;
|
|||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
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.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpUtil;
|
||||
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.listener.CookieListener;
|
||||
import org.xbib.netty.http.client.listener.ResponseListener;
|
||||
import org.xbib.netty.http.client.listener.StatusListener;
|
||||
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;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
|
@ -57,7 +79,7 @@ public class Request {
|
|||
|
||||
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,
|
||||
long timeoutInMillis, boolean followRedirect, int maxRedirect, int redirectCount,
|
||||
boolean isBackOff, BackOff backOff) {
|
||||
|
@ -148,16 +170,14 @@ public class Request {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Request[url='").append(url)
|
||||
.append("',version=").append(httpVersion)
|
||||
.append(",method=").append(httpMethod)
|
||||
.append(",headers=").append(headers.entries())
|
||||
.append(",content=").append(content != null && content.readableBytes() >= 16 ?
|
||||
content.copy(0,16).toString(StandardCharsets.UTF_8) + "..." :
|
||||
content != null ? content.toString(StandardCharsets.UTF_8) : "")
|
||||
.append("]");
|
||||
return sb.toString();
|
||||
return "Request[url='" + url +
|
||||
"',version=" + httpVersion +
|
||||
",method=" + httpMethod +
|
||||
",headers=" + headers.entries() +
|
||||
",content=" + (content != null && content.readableBytes() >= 16 ?
|
||||
content.copy(0, 16).toString(StandardCharsets.UTF_8) + "..." :
|
||||
content != null ? content.toString(StandardCharsets.UTF_8) : "") +
|
||||
"]";
|
||||
}
|
||||
|
||||
public Request setCompletableFuture(CompletableFuture<?> completableFuture) {
|
||||
|
@ -197,47 +217,398 @@ public class Request {
|
|||
return responseListener;
|
||||
}
|
||||
|
||||
public static RequestBuilder get() {
|
||||
public static Builder get() {
|
||||
return builder(HttpMethod.GET);
|
||||
}
|
||||
|
||||
public static RequestBuilder put() {
|
||||
public static Builder put() {
|
||||
return builder(HttpMethod.PUT);
|
||||
}
|
||||
|
||||
public static RequestBuilder post() {
|
||||
public static Builder post() {
|
||||
return builder(HttpMethod.POST);
|
||||
}
|
||||
|
||||
public static RequestBuilder delete() {
|
||||
public static Builder delete() {
|
||||
return builder(HttpMethod.DELETE);
|
||||
}
|
||||
|
||||
public static RequestBuilder head() {
|
||||
public static Builder head() {
|
||||
return builder(HttpMethod.HEAD);
|
||||
}
|
||||
|
||||
public static RequestBuilder patch() {
|
||||
public static Builder patch() {
|
||||
return builder(HttpMethod.PATCH);
|
||||
}
|
||||
|
||||
public static RequestBuilder trace() {
|
||||
public static Builder trace() {
|
||||
return builder(HttpMethod.TRACE);
|
||||
}
|
||||
|
||||
public static RequestBuilder options() {
|
||||
public static Builder options() {
|
||||
return builder(HttpMethod.OPTIONS);
|
||||
}
|
||||
|
||||
public static RequestBuilder connect() {
|
||||
public static Builder connect() {
|
||||
return builder(HttpMethod.CONNECT);
|
||||
}
|
||||
|
||||
public static RequestBuilder builder(HttpMethod httpMethod) {
|
||||
public static Builder builder(HttpMethod httpMethod) {
|
||||
return builder(PooledByteBufAllocator.DEFAULT, httpMethod);
|
||||
}
|
||||
|
||||
public static RequestBuilder builder(ByteBufAllocator allocator, HttpMethod httpMethod) {
|
||||
return new RequestBuilder(allocator).setMethod(httpMethod);
|
||||
public static Builder builder(ByteBufAllocator allocator, HttpMethod 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.ssl.ApplicationProtocolNames;
|
||||
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.ClientConfig;
|
||||
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.common.HttpAddress;
|
||||
import org.xbib.netty.http.client.Request;
|
||||
import org.xbib.netty.http.client.RequestBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
|
@ -89,7 +88,7 @@ public class RestClient {
|
|||
HttpMethod httpMethod) throws IOException {
|
||||
URL url = URL.create(urlString);
|
||||
RestClient restClient = new RestClient();
|
||||
RequestBuilder requestBuilder = Request.builder(httpMethod).url(url);
|
||||
Request.Builder requestBuilder = Request.builder(httpMethod).url(url);
|
||||
if (byteBuf != null) {
|
||||
requestBuilder.content(byteBuf);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ import org.xbib.net.URLSyntaxException;
|
|||
import org.xbib.netty.http.client.Client;
|
||||
import org.xbib.netty.http.common.HttpAddress;
|
||||
import org.xbib.netty.http.client.Request;
|
||||
import org.xbib.netty.http.client.RequestBuilder;
|
||||
import org.xbib.netty.http.client.retry.BackOff;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -243,7 +242,7 @@ abstract class BaseTransport implements Transport {
|
|||
logger.log(Level.FINE, "found redirect location: " + location);
|
||||
URL redirUrl = URL.base(request.url()).resolve(location);
|
||||
HttpMethod method = httpResponse.status().code() == 303 ? HttpMethod.GET : request.httpMethod();
|
||||
RequestBuilder newHttpRequestBuilder = Request.builder(method)
|
||||
Request.Builder newHttpRequestBuilder = Request.builder(method)
|
||||
.url(redirUrl)
|
||||
.setVersion(request.httpVersion())
|
||||
.setHeaders(request.headers())
|
||||
|
|
|
@ -28,7 +28,7 @@ class Http1Test {
|
|||
" status=" + msg.status().code()));
|
||||
client.execute(request).get();
|
||||
} finally {
|
||||
client.shutdown();
|
||||
client.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ class Http1Test {
|
|||
msg.content().toString(StandardCharsets.UTF_8)));
|
||||
client.execute(request2).get();
|
||||
} finally {
|
||||
client.shutdown();
|
||||
client.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import io.netty.handler.codec.http.HttpMethod;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.xbib.net.URL;
|
||||
import org.xbib.netty.http.client.Request;
|
||||
import org.xbib.netty.http.client.RequestBuilder;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
@ -35,7 +34,7 @@ class RequestBuilderTest {
|
|||
|
||||
@Test
|
||||
void testRelativeUri() {
|
||||
RequestBuilder httpRequestBuilder = Request.get();
|
||||
Request.Builder httpRequestBuilder = Request.get();
|
||||
httpRequestBuilder.url("https://localhost").uri("/path");
|
||||
assertEquals("/path", httpRequestBuilder.build().relative());
|
||||
httpRequestBuilder.uri("/foobar");
|
||||
|
@ -68,17 +67,17 @@ class RequestBuilderTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void testPostRequest() {
|
||||
void testBasicPostRequest() {
|
||||
Request request = Request.builder(HttpMethod.POST)
|
||||
.url("http://xbib.org")
|
||||
.addParameter("param1", "value1")
|
||||
.addParameter("param2", "value2")
|
||||
.content("Hello", "text/plain")
|
||||
.content("a=b&c=d", "application/x-www-form-urldencoded")
|
||||
.build();
|
||||
assertEquals("xbib.org", request.url().getHost());
|
||||
assertEquals("?param1=value1¶m2=value2", request.relative());
|
||||
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
|
||||
|
@ -112,7 +111,7 @@ class RequestBuilderTest {
|
|||
|
||||
@Test
|
||||
void testMassiveQueryParameters() {
|
||||
RequestBuilder requestBuilder = Request.builder(HttpMethod.GET);
|
||||
Request.Builder requestBuilder = Request.builder(HttpMethod.GET);
|
||||
for (int i = 0; i < 2000; i++) {
|
||||
requestBuilder.addParameter("param" + i, "value" + i);
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ class SecureHttpTest {
|
|||
" status=" + msg.status().code()));
|
||||
client.execute(request).get();
|
||||
} finally {
|
||||
client.shutdown();
|
||||
client.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ class SecureHttpTest {
|
|||
msg.content().toString(StandardCharsets.UTF_8)));
|
||||
client.execute(request2).get();
|
||||
} finally {
|
||||
client.shutdown();
|
||||
client.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ class WebtideTest {
|
|||
.setResponseListener(msg -> logger.log(Level.INFO, "got response: " + msg));
|
||||
client.execute(request).get();
|
||||
} 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.StandardCharsets;
|
||||
import java.nio.charset.UnmappableCharacterException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -28,6 +30,12 @@ import java.util.SortedSet;
|
|||
*/
|
||||
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 sizeLimit;
|
||||
|
@ -40,17 +48,24 @@ public class HttpParameters implements Map<String, SortedSet<String>> {
|
|||
|
||||
private final PercentDecoder percentDecoder;
|
||||
|
||||
private final String contentType;
|
||||
|
||||
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.sizeLimit = sizeLimit;
|
||||
this.elementSizeLimit = elementSizeLimit;
|
||||
this.map = new LimitedStringMap(maxParam);
|
||||
this.percentEncoder = PercentEncoders.getQueryEncoder(StandardCharsets.UTF_8);
|
||||
this.percentDecoder = new PercentDecoder();
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -259,6 +274,18 @@ public class HttpParameters implements Map<String, SortedSet<String>> {
|
|||
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
|
||||
* 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;
|
||||
SortedSet<String> values = map.get(k);
|
||||
if (values == null) {
|
||||
return k + "=";
|
||||
return k + EQUALS;
|
||||
}
|
||||
Iterator<String> it = values.iterator();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
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()) {
|
||||
sb.append("&");
|
||||
sb.append(AMPERSAND);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
|
@ -311,9 +340,15 @@ public class HttpParameters implements Map<String, SortedSet<String>> {
|
|||
}
|
||||
|
||||
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_"))
|
||||
.forEach(entry -> oauthParams.put(entry.getKey(), entry.getValue()));
|
||||
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;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
@ -1153,13 +1154,13 @@ public class MediaType {
|
|||
private final String contentType;
|
||||
|
||||
private MediaType(String contentType) {
|
||||
this.bytes = contentType.getBytes();
|
||||
this.bytes = contentType.getBytes(StandardCharsets.UTF_8);
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
||||
private MediaType(String name, String[] attributes) {
|
||||
this.bytes = join(name, attributes).getBytes();
|
||||
this.contentType = new String(this.bytes);
|
||||
this.bytes = join(name, attributes).getBytes(StandardCharsets.UTF_8);
|
||||
this.contentType = new String(this.bytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private static MediaType create(String type, String... fileExtensisons) {
|
||||
|
|
|
@ -193,6 +193,7 @@ public final class Server {
|
|||
}
|
||||
|
||||
public synchronized void shutdownGracefully() throws IOException {
|
||||
logger.log(Level.FINE, "shutting down gracefully");
|
||||
// first, shut down threads, then server socket
|
||||
childEventLoopGroup.shutdownGracefully();
|
||||
parentEventLoopGroup.shutdownGracefully();
|
||||
|
|
|
@ -2,8 +2,10 @@ package org.xbib.netty.http.server;
|
|||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import org.xbib.netty.http.common.HttpParameters;
|
||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -19,9 +21,13 @@ public interface ServerRequest {
|
|||
|
||||
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();
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ public class Endpoint {
|
|||
for (QueryParameters.Pair<String, String> pair : queryParameters) {
|
||||
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.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class EndpointResolver {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(EndpointResolver.class.getName());
|
||||
|
||||
private final Endpoint defaultEndpoint;
|
||||
|
||||
private final List<Endpoint> endpoints;
|
||||
|
@ -44,9 +40,6 @@ public class EndpointResolver {
|
|||
.filter(endpoint -> endpoint.matches(endpointInfo))
|
||||
.sorted(new Endpoint.EndpointPathComparator(serverRequest.getEffectiveRequestPath())).collect(Collectors.toList()));
|
||||
List<Endpoint> matchingEndpoints = cache.get(endpointInfo);
|
||||
if (logger.isLoggable(Level.FINER)) {
|
||||
logger.log(Level.FINER, "matching endpoints = " + matchingEndpoints);
|
||||
}
|
||||
if (matchingEndpoints.isEmpty()) {
|
||||
if (defaultEndpoint != null) {
|
||||
defaultEndpoint.resolveUriTemplate(serverRequest);
|
||||
|
@ -148,8 +141,9 @@ public class EndpointResolver {
|
|||
}
|
||||
|
||||
/**
|
||||
* Add endpoint.
|
||||
*
|
||||
* @param endpoint
|
||||
* @param endpoint the endpoint
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder addEndpoint(Endpoint endpoint) {
|
||||
|
@ -164,6 +158,8 @@ public class EndpointResolver {
|
|||
/**
|
||||
* Adds a service for the methods of the given object that
|
||||
* are annotated with the {@link Context} annotation.
|
||||
* @param classWithAnnotatedMethods class with annotated methods
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder addEndpoint(Object classWithAnnotatedMethods) {
|
||||
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.Provider;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
@ -258,6 +259,13 @@ public class NamedServer {
|
|||
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() {
|
||||
if (httpAddress.isSecure()) {
|
||||
try {
|
||||
|
|
|
@ -8,44 +8,87 @@ import org.xbib.netty.http.server.ServerResponse;
|
|||
import org.xbib.netty.http.server.util.MimeTypeUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.MappedByteBuffer;
|
||||
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.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
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;
|
||||
|
||||
public ClasspathService(ClassLoader classLoader, String prefix) {
|
||||
this.classLoader = classLoader;
|
||||
private final Map<String, String> env;
|
||||
|
||||
public ClasspathService(Class<?> clazz, String prefix) {
|
||||
this.clazz = clazz;
|
||||
this.prefix = prefix;
|
||||
this.env = new HashMap<>();
|
||||
env.put("create", "true");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
||||
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) {
|
||||
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) {
|
||||
try {
|
||||
FileChannel fileChannel = (FileChannel) Files.newByteChannel(Paths.get(url.toURI()));
|
||||
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());
|
||||
ByteBuf byteBuf = Unpooled.wrappedBuffer(mappedByteBuffer);
|
||||
try {
|
||||
String contentType = MimeTypeUtils.guessFromPath(requestPath, false);
|
||||
serverResponse.write(HttpResponseStatus.OK, contentType, byteBuf);
|
||||
} finally {
|
||||
byteBuf.release();
|
||||
if ("jar".equals(url.getProtocol())) {
|
||||
doJarResource(url.toURI(), contentType, serverResponse);
|
||||
} else {
|
||||
doFileResource(url.toURI(), contentType, serverResponse);
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
logger.log(Level.SEVERE, e.getMessage(), e);
|
||||
serverResponse.write(HttpResponseStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
} else {
|
||||
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
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,30 +1,47 @@
|
|||
package org.xbib.netty.http.server.transport;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
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.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.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.
|
||||
*/
|
||||
public class HttpServerRequest implements ServerRequest {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(HttpServerRequest.class.getName());
|
||||
|
||||
private static final String PATH_SEPARATOR = "/";
|
||||
|
||||
private static final CharSequence APPLICATION_FORM_URL_ENCODED = "application/x-www-form-urlencoded";
|
||||
|
||||
private NamedServer namedServer;
|
||||
|
||||
private ChannelHandlerContext ctx;
|
||||
|
||||
private List<String> context;
|
||||
|
||||
private Map<String, String> rawParameters;
|
||||
private Map<String, String> pathParameters;
|
||||
|
||||
private FullHttpRequest httpRequest;
|
||||
|
||||
private HttpParameters parameters;
|
||||
|
||||
private Integer sequenceId;
|
||||
|
||||
private Integer streamId;
|
||||
|
@ -79,13 +96,27 @@ public class HttpServerRequest implements ServerRequest {
|
|||
uri.substring(getContextPath().length() + 2) : uri;
|
||||
}
|
||||
|
||||
public void setRawParameters(Map<String, String> rawParameters) {
|
||||
this.rawParameters = rawParameters;
|
||||
public void setPathParameters(Map<String, String> pathParameters) {
|
||||
this.pathParameters = pathParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getRawParameters() {
|
||||
return rawParameters;
|
||||
public Map<String, String> getPathParameters() {
|
||||
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) {
|
||||
|
@ -115,6 +146,26 @@ public class HttpServerRequest implements ServerRequest {
|
|||
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() {
|
||||
return "ServerRequest[namedServer=" + namedServer +
|
||||
",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;
|
||||
|
||||
@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
|
||||
void testSecureStaticFileServerHttp1() throws Exception {
|
|
@ -22,9 +22,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@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
|
||||
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