update to groovy 4.0.11, netty 4.1.90, xbib net 3.1.0, use multimap for HTTP request

This commit is contained in:
Jörg Prante 2023-04-03 17:17:58 +02:00
parent ae1240822f
commit 845260a14e
16 changed files with 176 additions and 136 deletions

View file

@ -1,5 +1,5 @@
group = org.xbib
name = netty-http
version = 4.1.85.0
version = 4.1.90.0
org.gradle.warning.mode = ALL

View file

@ -7,7 +7,7 @@ dependencies {
test {
useJUnitPlatform()
failFast = true
failFast = false
maxHeapSize '1g'
systemProperty 'java.util.logging.config.file', 'src/test/resources/logging.properties'
testLogging {

View file

@ -42,7 +42,7 @@ import java.util.concurrent.CompletableFuture;
/**
* HTTP client request.
*/
public final class Request implements AutoCloseable {
public final class Request {
private final URL url;
@ -202,7 +202,6 @@ public final class Request implements AutoCloseable {
}
}
@Override
public void close() throws IOException {
release();
}

View file

@ -63,21 +63,19 @@ public class RestClient {
HttpMethod httpMethod) throws IOException {
URL url = URL.create(urlString);
RestClient restClient = new RestClient();
try (Client client = Client.builder()
Client client = Client.builder()
.setThreadCount(2) // for redirect
.build()) {
Request.Builder requestBuilder = Request.builder(httpMethod).url(url);
if (body != null) {
ByteBuf byteBuf = client.getByteBufAllocator().buffer();
byteBuf.writeCharSequence(body, charset);
requestBuilder.content(byteBuf);
}
client.newTransport(HttpAddress.http1(url))
.execute(requestBuilder.setResponseListener(restClient::setResponse).build())
.close();
} catch (Exception e) {
throw new IOException(e);
.build();
Request.Builder requestBuilder = Request.builder(httpMethod).url(url);
if (body != null) {
ByteBuf byteBuf = client.getByteBufAllocator().buffer();
byteBuf.writeCharSequence(body, charset);
requestBuilder.content(byteBuf);
}
client.newTransport(HttpAddress.http1(url))
.execute(requestBuilder.setResponseListener(restClient::setResponse).build())
.close();
client.close();
return restClient;
}
}

View file

@ -61,7 +61,7 @@ import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
public final class Client implements AutoCloseable {
public final class Client {
private static final Logger logger = Logger.getLogger(Client.class.getName());
@ -366,7 +366,6 @@ public final class Client implements AutoCloseable {
closeAndRemove(transport);
}
@Override
public void close() throws IOException {
shutdownGracefully();
}

View file

@ -128,17 +128,21 @@ class RequestBuilderTest {
.url("https://google.com? a = b")
.build();
assertEquals("google.com", request.url().getHost());
assertEquals("https://google.com?%20a%20=%20b", request.absolute());
assertEquals("?%20a%20=%20b", request.relative());
assertEquals("https://google.com? a = b", request.absolute());
assertEquals("? a = b", request.relative());
assertEquals(" a = b", request.url().getQuery());
assertEquals(" a = b", request.url().getDecodedQuery());
assertEquals("https://google.com? a = b", request.url().toString());
assertEquals("https://google.com?%20a%20=%20b", request.url().toExternalForm());
assertEquals("https://google.com? a = b", request.url().toExternalForm());
request = Request.get()
.url("https://google.com?%20a%20=%20b")
.build();
assertEquals("google.com", request.url().getHost());
assertEquals("https://google.com?%20a%20=%20b", request.absolute());
assertEquals("?%20a%20=%20b", request.relative());
assertEquals("https://google.com? a = b", request.url().toString());
assertEquals("%20a%20=%20b", request.url().getQuery());
assertEquals(" a = b", request.url().getDecodedQuery());
assertEquals("https://google.com?%20a%20=%20b", request.url().toString());
assertEquals("https://google.com?%20a%20=%20b", request.url().toExternalForm());
}
@ -149,6 +153,10 @@ class RequestBuilderTest {
requestBuilder.addParameter("param" + i, "value" + i);
}
Request request = requestBuilder.build();
assertEquals(18276, request.absolute().length());
assertEquals(37796, request.absolute().length());
Request parsedRequest = Request.get()
.url(request.url())
.build();
assertEquals(37796, parsedRequest.absolute().length());
}
}

View file

@ -22,14 +22,14 @@ class FileDescriptorLeakTest {
if (os instanceof UnixOperatingSystemMXBean) {
logger.info("before: number of open file descriptor : " + ((UnixOperatingSystemMXBean) os).getOpenFileDescriptorCount());
}
try (Client client = Client.builder().setThreadCount(1).build()) {
Request request = Request.get().url("http://xbib.org")
.setResponseListener(resp -> {
logger.log(Level.INFO, "status = " + resp.getStatus());
})
.build();
client.execute(request);
}
Client client = Client.builder().setThreadCount(1).build();
Request request = Request.get().url("http://xbib.org")
.setResponseListener(resp -> {
logger.log(Level.INFO, "status = " + resp.getStatus());
})
.build();
client.execute(request);
client.close();
if (os instanceof UnixOperatingSystemMXBean){
logger.info("after: number of open file descriptor : " + ((UnixOperatingSystemMXBean) os).getOpenFileDescriptorCount());
}

View file

@ -13,17 +13,17 @@ public class XbibTest {
@Test
void testXbib() throws Exception {
try (Client client = Client.builder()
Client client = Client.builder()
.enableDebug()
.build()) {
Request request = Request.get()
.url("https://xbib.org/")
.setVersion("HTTP/2.0")
.setResponseListener(resp -> logger.log(Level.INFO, "got HTTP/2 response: " +
resp.getHeaders() + resp.getBodyAsString(StandardCharsets.UTF_8)))
.build();
client.execute(request).get().close();
}
.build();
Request request = Request.get()
.url("https://xbib.org/")
.setVersion("HTTP/2.0")
.setResponseListener(resp -> logger.log(Level.INFO, "got HTTP/2 response: " +
resp.getHeaders() + resp.getBodyAsString(StandardCharsets.UTF_8)))
.build();
client.execute(request).get().close();
client.close();
}
}

View file

@ -1,9 +1,13 @@
package org.xbib.netty.http.common;
import io.netty.handler.codec.http.HttpHeaderValues;
import org.xbib.datastructures.common.MultiMap;
import org.xbib.datastructures.common.Pair;
import org.xbib.datastructures.common.TreeMultiMap;
import org.xbib.net.PercentDecoder;
import org.xbib.net.PercentEncoder;
import org.xbib.net.PercentEncoders;
import org.xbib.netty.http.common.util.CaseInsensitiveParameters;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.MalformedInputException;
@ -13,26 +17,26 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A limited multi-map of HTTP request parameters. Each key references a
* limited set of parameters collected from the request during message
* signing. Parameter values are sorted as per
* <a href="http://oauth.net/core/1.0a/#anchor13">OAuth specification</a>.
* A multi-map of HTTP request parameters. Each key references a
* set of parameters collected from the request during message
* signing.
* Every key/value pair will be percent-encoded upon insertion.
* This class has special semantics tailored to
* being useful for message signing; it's not a general purpose collection class
* to handle request parameters.
*/
public class HttpParameters extends CaseInsensitiveParameters {
@SuppressWarnings("serial")
public class HttpParameters {
private static final String EQUALS = "=";
private static final Logger logger = Logger.getLogger(HttpParameters.class.getName());
private static final String AMPERSAND = "&";
private static final char EQUAL_CHAR = '=';
private static final char AMPERSAND_CHAR = '&';
private final int sizeLimit;
private final MultiMap<String, String> multiMap;
private final int elementSizeLimit;
private final PercentDecoder percentDecoder;
private final PercentEncoder percentEncoder;
@ -41,24 +45,16 @@ public class HttpParameters extends CaseInsensitiveParameters {
private final Charset encoding;
public HttpParameters() {
this(1024, 1024, 65536,
HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED, StandardCharsets.UTF_8);
this(HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED, StandardCharsets.UTF_8);
}
public HttpParameters(CharSequence contentType) {
this(1024, 1024, 65536,
contentType, StandardCharsets.UTF_8);
this(contentType, StandardCharsets.UTF_8);
}
public HttpParameters(CharSequence contentType, Charset charset) {
this(1024, 1024, 65536,
contentType, charset);
}
public HttpParameters(int maxParam, int sizeLimit, int elementSizeLimit,
CharSequence contentType, Charset charset) {
this.sizeLimit = sizeLimit;
this.elementSizeLimit = elementSizeLimit;
this.multiMap = new TreeMultiMap<>();
this.percentDecoder = new PercentDecoder();
this.percentEncoder = PercentEncoders.getQueryEncoder(charset);
this.contentType = contentType;
this.encoding = charset;
@ -72,12 +68,26 @@ public class HttpParameters extends CaseInsensitiveParameters {
return encoding;
}
public Collection<String> put(String key, Collection<String> values, boolean percentEncode) {
remove(key);
public String get(String key) {
Collection<String> collection = multiMap.get(key);
if (collection != null) {
Iterator<String> iterator = collection.iterator();
if (iterator.hasNext()) {
return iterator.next();
}
}
return null;
}
public Collection<String> getAll(String key) {
return multiMap.get(key);
}
public void put(String key, Collection<String> values, boolean percentEncode) {
multiMap.remove(key);
for (String v : values) {
add(key, v, percentEncode);
}
return getAll(key);
}
/**
@ -121,7 +131,7 @@ public class HttpParameters extends CaseInsensitiveParameters {
try {
String k = percentEncode ? percentEncoder.encode(key) : key;
String v = percentEncode ? percentEncoder.encode(value) : value;
super.add(k, v);
multiMap.put(k, v);
} catch (CharacterCodingException e) {
throw new IllegalArgumentException(e);
}
@ -130,10 +140,10 @@ public class HttpParameters extends CaseInsensitiveParameters {
public String getAsQueryString(boolean percentEncode) throws MalformedInputException, UnmappableCharacterException {
List<String> list = new ArrayList<>();
for (String key : super.names()) {
for (String key : multiMap.keySet()) {
list.add(getAsQueryString(key, percentEncode));
}
return String.join(AMPERSAND, list);
return String.join(String.valueOf(AMPERSAND_CHAR), list);
}
/**
@ -164,20 +174,46 @@ public class HttpParameters extends CaseInsensitiveParameters {
public String getAsQueryString(String key, boolean percentEncode)
throws MalformedInputException, UnmappableCharacterException {
String k = percentEncode ? percentEncoder.encode(key) : key;
Collection<String> values = getAll(k);
if (values == null) {
return k + EQUALS;
}
Collection<String> values = multiMap.asMap().get(key);
Iterator<String> it = values.iterator();
StringBuilder sb = new StringBuilder();
while (it.hasNext()) {
String v = it.next();
v = percentEncode ? percentEncoder.encode(v) : v;
sb.append(k).append(EQUALS).append(v);
sb.append(k).append(EQUAL_CHAR).append(v);
if (it.hasNext()) {
sb.append(AMPERSAND);
sb.append(AMPERSAND_CHAR);
}
}
return sb.toString();
}
public void addPercentEncodedBody(String body) {
if (body == null) {
return;
}
// watch out for "plus" encoding, replace it with a space character
String s = body.replace('+', ' ');
while (s != null) {
Pair<String, Object> pairs = indexOf(AMPERSAND_CHAR, s);
Pair<String, Object> pair = indexOf(EQUAL_CHAR, pairs.getKey());
if (pair.getKey() != null && !pair.getKey().isEmpty()) {
try {
String key = percentDecoder.decode(pair.getKey());
String value = pair.getValue() instanceof CharSequence ? percentDecoder.decode((CharSequence) pair.getValue()) : pair.getValue().toString();
addRaw(key, value);
} catch (MalformedInputException | UnmappableCharacterException e) {
logger.log(Level.WARNING, "unable to decode parameter in body: " + e.getMessage(), e);
}
}
s = pairs.getValue() !=null ? pairs.getValue().toString() : null;
}
}
private static Pair<String, Object> indexOf(char ch, String input) {
int i = input.indexOf(ch);
String k = i >= 0 ? input.substring(0, i) : input;
Object v = i >= 0 ? input.substring(i + 1) : null;
return Pair.of(k, v);
}
}

View file

@ -0,0 +1,5 @@
handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler
.level=ALL
java.util.logging.ConsoleHandler.level=ALL
java.util.logging.ConsoleHandler.formatter=org.xbib.net.util.ThreadLoggingFormatter
jdk.event.security.level=INFO

View file

@ -7,10 +7,6 @@ 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 org.xbib.datastructures.common.Pair;
import org.xbib.net.Parameter;
import org.xbib.net.ParameterBuilder;
import org.xbib.net.PercentDecoder;
import org.xbib.net.URL;
import org.xbib.netty.http.common.HttpParameters;
import org.xbib.netty.http.server.api.Domain;
@ -363,42 +359,31 @@ public class HttpServerRequest implements ServerRequest {
public ServerRequest build() {
// build URL and parameters
Charset charset = HttpUtil.getCharset(fullHttpRequest, StandardCharsets.UTF_8);
CharSequence mimeType = HttpUtil.getMimeType(fullHttpRequest);
this.parameters = new HttpParameters(mimeType, charset);
// creates path, query params, fragment
this.url = URL.builder()
.charset(charset, CodingErrorAction.REPLACE)
.path(fullHttpRequest.uri()) // creates path, query params, fragment
.build();
ParameterBuilder queryParameters = Parameter.builder();
//url.getQueryParams();
CharSequence mimeType = HttpUtil.getMimeType(fullHttpRequest);
ByteBuf byteBuf = fullHttpRequest.content();
if (byteBuf != null) {
if (fullHttpRequest.method().equals(HttpMethod.POST)) {
String params;
url.getQueryParams().forEach(p -> parameters.add(p.getKey(), p.getValue().toString()));
if (fullHttpRequest.method().equals(HttpMethod.POST)) {
ByteBuf byteBuf = fullHttpRequest.content();
if (byteBuf != null) {
Charset htmlCharset = HttpUtil.getCharset(fullHttpRequest, StandardCharsets.ISO_8859_1);
String body = byteBuf.toString(htmlCharset);
// https://www.w3.org/TR/html4/interact/forms.html#h-17.13.4
if (mimeType != null && HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED.toString().equals(mimeType.toString())) {
Charset htmlCharset = HttpUtil.getCharset(fullHttpRequest, StandardCharsets.ISO_8859_1);
params = byteBuf.toString(htmlCharset).replace('+', ' ');
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER, "html form, charset = " + htmlCharset + " param body = " + params);
}
queryParameters.addPercentEncodedBody(params);
logger.log(Level.INFO, "html form, charset = " + htmlCharset + " body = " + body);
parameters.addPercentEncodedBody(body);
} else {
logger.log(Level.WARNING, "unable to process POST request, mime type = " + mimeType);
}
} else {
logger.log(Level.WARNING, "unable to process POST request,body is empty");
}
}
// copy to HTTP parameters but percent-decoded (looks very clumsy)
PercentDecoder percentDecoder = new PercentDecoder(charset.newDecoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE));
this.parameters = new HttpParameters(mimeType, charset);
for (Pair<String, Object> pair : queryParameters.build()) {
try {
parameters.addRaw(percentDecoder.decode(pair.getKey()), percentDecoder.decode(pair.getValue().toString()));
} catch (Exception e) {
// does not happen
throw new IllegalArgumentException(pair.toString());
}
}
logger.log(Level.FINER, "HTTP server complete");
return new HttpServerRequest(this);
}

View file

@ -56,7 +56,7 @@ import java.util.logging.Logger;
/**
* HTTP server.
*/
public final class Server implements AutoCloseable {
public final class Server {
private static final Logger logger = Logger.getLogger(Server.class.getName());
@ -168,7 +168,6 @@ public final class Server implements AutoCloseable {
accept().channel().closeFuture().sync();
}
@Override
public void close() {
try {
shutdownGracefully();

View file

@ -1,5 +1,6 @@
package org.xbib.netty.http.server.test.http1;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -34,7 +35,9 @@ class PostTest {
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
.singleEndpoint("/post", "/**", (req, resp) -> {
HttpParameters parameters = req.getParameters();
logger.log(Level.INFO, "got request " + parameters.toString() + ", sending OK");
logger.log(Level.INFO, "server got request " + parameters +
" body = " + req.getContent(StandardCharsets.UTF_8) +
" withspace = " + parameters.get("withspace"));
if ("Hello World".equals(parameters.get("withspace"))) {
success2.set(true);
}
@ -86,7 +89,7 @@ class PostTest {
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
.singleEndpoint("/post", "/**", (req, resp) -> {
HttpParameters parameters = req.getParameters();
logger.log(Level.INFO, "got request " + parameters.toString() + ", sending OK");
logger.log(Level.INFO, "server got request " + parameters + " body = " + req.getContent(StandardCharsets.UTF_8));
if ("Hello World".equals(parameters.get("withspace"))) {
success2.set(true);
}
@ -128,7 +131,7 @@ class PostTest {
}
@Test
void testFormPostHttp1() throws Exception {
void testFormPostHttp1WithCorrectContentType() throws Exception {
final AtomicBoolean success1 = new AtomicBoolean(false);
final AtomicBoolean success2 = new AtomicBoolean(false);
final AtomicBoolean success3 = new AtomicBoolean(false);
@ -137,7 +140,7 @@ class PostTest {
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
.singleEndpoint("/post", "/**", (req, resp) -> {
HttpParameters parameters = req.getParameters();
logger.log(Level.INFO, "got request " + parameters.toString() + ", sending OK");
logger.log(Level.INFO, "server got request " + parameters + " body = " + req.getContent(StandardCharsets.UTF_8));
if ("Hello World".equals(parameters.get("withplus"))) {
success2.set(true);
}
@ -164,6 +167,7 @@ class PostTest {
};
Request postRequest = Request.post().setVersion(HttpVersion.HTTP_1_1)
.url(server.getServerConfig().getAddress().base().resolve("/post/test.txt"))
// the corrent content type
.contentType(HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED, StandardCharsets.UTF_8)
.addParameter("a", "b")
// test 'plus' encoding
@ -185,7 +189,7 @@ class PostTest {
}
@Test
void testTextPlainPostHttp1() throws Exception {
void testTextPlainPostHttp1MustFail() throws Exception {
final AtomicBoolean success1 = new AtomicBoolean(false);
final AtomicBoolean success2 = new AtomicBoolean(false);
final AtomicBoolean success3 = new AtomicBoolean(false);
@ -194,7 +198,7 @@ class PostTest {
HttpServerDomain domain = HttpServerDomain.builder(httpAddress)
.singleEndpoint("/post", "/**", (req, resp) -> {
HttpParameters parameters = req.getParameters();
logger.log(Level.INFO, "got request " + parameters.toString() + ", sending OK");
logger.log(Level.INFO, "server got request " + parameters + " body = " + req.getContent(StandardCharsets.UTF_8));
if ("Hello World".equals(parameters.get("withoutplus"))) {
success2.set(true);
}
@ -223,7 +227,7 @@ class PostTest {
.setVersion(HttpVersion.HTTP_1_1)
.url(server.getServerConfig().getAddress().base().resolve("/post/test.txt"))
.contentType(HttpHeaderValues.TEXT_PLAIN, StandardCharsets.UTF_8)
// you can not pass form parameters on content type "text/plain"
// you can not pass form parameters on content type "text/plain" :-)
.addParameter("a", "b")
.addParameter("my param", "my value")
.addParameter("withoutplus", "Hello World")
@ -237,9 +241,9 @@ class PostTest {
logger.log(Level.INFO, "server and client shut down");
}
assertTrue(success1.get());
assertTrue(success2.get());
assertTrue(success3.get());
assertTrue(success4.get());
assertFalse(success2.get());
assertFalse(success3.get());
assertFalse(success4.get());
}
@Test
@ -252,10 +256,11 @@ class PostTest {
.singleEndpoint("/post", "/**", (req, resp) -> {
HttpParameters parameters = req.getParameters();
logger.log(Level.INFO, "got request " + parameters.toString() + ", sending OK");
// unable to decode without underflow
if ("myÿvalue".equals(parameters.get("my param"))) {
success1.set(true);
}
if ("bÿc".equals(parameters.get("a"))) {
if ("b%25YYc".equals(parameters.get("a"))) {
success2.set(true);
}
resp.getBuilder().setStatus(HttpResponseStatus.OK.code()).build().flush();
@ -285,9 +290,8 @@ class PostTest {
client.shutdownGracefully();
logger.log(Level.INFO, "server and client shut down");
}
assertTrue(success1.get());
assertFalse(success1.get());
assertTrue(success2.get());
assertTrue(success3.get());
}
}

View file

@ -20,6 +20,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(NettyHttpTestExtension.class)
@ -240,9 +241,9 @@ class PostTest {
logger.log(Level.INFO, "server and client shut down");
}
assertTrue(success1.get());
assertTrue(success2.get());
assertTrue(success3.get());
assertTrue(success4.get());
assertFalse(success2.get());
assertFalse(success3.get());
assertFalse(success4.get());
}
@Test
@ -255,10 +256,11 @@ class PostTest {
.singleEndpoint("/post", "/**", (req, resp) -> {
HttpParameters parameters = req.getParameters();
logger.log(Level.INFO, "got request " + parameters.toString() + ", sending OK");
// unable to decode
if ("myÿvalue".equals(parameters.get("my param"))) {
success1.set(true);
}
if ("bÿc".equals(parameters.get("a"))) {
if ("b%25YYc".equals(parameters.get("a"))) {
success2.set(true);
}
resp.getBuilder().setStatus(HttpResponseStatus.OK.code()).build().flush();
@ -289,7 +291,7 @@ class PostTest {
client.shutdownGracefully();
logger.log(Level.INFO, "server and client shut down");
}
assertTrue(success1.get());
assertFalse(success1.get());
assertTrue(success2.get());
assertTrue(success3.get());
}

View file

@ -0,0 +1,5 @@
handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler
.level=ALL
java.util.logging.ConsoleHandler.level=ALL
java.util.logging.ConsoleHandler.formatter=org.xbib.net.util.ThreadLoggingFormatter
jdk.event.security.level=INFO

View file

@ -10,10 +10,10 @@ dependencyResolutionManagement {
versionCatalogs {
libs {
version('gradle', '7.5.1')
version('groovy', '3.0.10')
version('spock', '2.0-groovy-3.0')
version('junit', '5.9.1')
version('netty', '4.1.89.Final')
version('groovy', '4.0.11')
version('spock', '2.4-M1-groovy-4.0')
version('junit', '5.9.2')
version('netty', '4.1.90.Final')
version('netty-tcnative', '2.0.59.Final')
library('groovy-core', 'org.codehaus.groovy', 'groovy').versionRef('groovy')
library('spock-core', 'org.spockframework', 'spock-core').versionRef('spock')
@ -27,13 +27,13 @@ dependencyResolutionManagement {
library('netty-epoll', 'io.netty', 'netty-transport-native-epoll').versionRef('netty')
library('netty-kqueue', 'io.netty', 'netty-transport-native-kqueue').versionRef('netty')
library('netty-boringssl', 'io.netty', 'netty-tcnative-boringssl-static').versionRef('netty-tcnative')
library('net', 'org.xbib', 'net').version('3.0.3')
library('net-path', 'org.xbib', 'net-path').version('3.0.3')
library('bouncycastle', 'org.bouncycastle', 'bcpkix-jdk18on').version('1.72')
library('conscrypt', 'org.conscrypt', 'conscrypt-openjdk-uber').version('2.5.2')
library('jackson', 'com.fasterxml.jackson.core', 'jackson-databind').version('2.12.7')
library('guice', 'org.xbib', 'guice').version('5.0.1.0')
library('javassist', 'org.javassist', 'javassist').version('3.29.1-GA')
library('guice', 'org.xbib', 'guice').version('5.0.1.0')
library('net', 'org.xbib', 'net').version('3.1.0')
library('net-path', 'org.xbib', 'net-path').version('3.1.0')
}
}
}