add secure cookies, xmlrpc common
This commit is contained in:
parent
f322697702
commit
8975cd0978
151 changed files with 6904 additions and 723 deletions
21
build.gradle
21
build.gradle
|
@ -1,6 +1,3 @@
|
||||||
import java.time.ZonedDateTime
|
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "com.github.spotbugs" version "2.0.0"
|
id "com.github.spotbugs" version "2.0.0"
|
||||||
id "org.sonarqube" version "2.6.1"
|
id "org.sonarqube" version "2.6.1"
|
||||||
|
@ -8,19 +5,6 @@ plugins {
|
||||||
id "org.xbib.gradle.plugin.asciidoctor" version "1.5.6.0.1"
|
id "org.xbib.gradle.plugin.asciidoctor" version "1.5.6.0.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
printf "Date: %s\nHost: %s\nOS: %s %s %s\nJVM: %s %s %s %s\nGradle: %s Groovy: %s Java: %s\n" +
|
|
||||||
"Build: group: ${project.group} name: ${project.name} version: ${project.version}\n",
|
|
||||||
ZonedDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME),
|
|
||||||
InetAddress.getLocalHost(),
|
|
||||||
System.getProperty("os.name"),
|
|
||||||
System.getProperty("os.arch"),
|
|
||||||
System.getProperty("os.version"),
|
|
||||||
System.getProperty("java.version"),
|
|
||||||
System.getProperty("java.vm.version"),
|
|
||||||
System.getProperty("java.vm.vendor"),
|
|
||||||
System.getProperty("java.vm.name"),
|
|
||||||
gradle.gradleVersion, GroovySystem.getVersion(), JavaVersion.current()
|
|
||||||
|
|
||||||
apply plugin: 'org.xbib.gradle.plugin.asciidoctor'
|
apply plugin: 'org.xbib.gradle.plugin.asciidoctor'
|
||||||
apply plugin: "io.codearte.nexus-staging"
|
apply plugin: "io.codearte.nexus-staging"
|
||||||
|
|
||||||
|
@ -31,7 +15,6 @@ subprojects {
|
||||||
apply plugin: "com.github.spotbugs"
|
apply plugin: "com.github.spotbugs"
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
alpnagent
|
|
||||||
asciidoclet
|
asciidoclet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +22,6 @@ subprojects {
|
||||||
testImplementation "org.junit.jupiter:junit-jupiter-api:${project.property('junit.version')}"
|
testImplementation "org.junit.jupiter:junit-jupiter-api:${project.property('junit.version')}"
|
||||||
testImplementation "org.junit.jupiter:junit-jupiter-params:${project.property('junit.version')}"
|
testImplementation "org.junit.jupiter:junit-jupiter-params:${project.property('junit.version')}"
|
||||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${project.property('junit.version')}"
|
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${project.property('junit.version')}"
|
||||||
alpnagent "org.mortbay.jetty.alpn:jetty-alpn-agent:${project.property('alpnagent.version')}"
|
|
||||||
asciidoclet "org.asciidoctor:asciidoclet:${project.property('asciidoclet.version')}"
|
asciidoclet "org.asciidoctor:asciidoclet:${project.property('asciidoclet.version')}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,9 +64,6 @@ subprojects {
|
||||||
"${result.skippedTestCount} skipped"
|
"${result.skippedTestCount} skipped"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (JavaVersion.current() == JavaVersion.VERSION_1_8) {
|
|
||||||
jvmArgs "-javaagent:" + configurations.alpnagent.asPath
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clean {
|
clean {
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
group = org.xbib
|
group = org.xbib
|
||||||
name = netty-http
|
name = netty-http
|
||||||
version = 4.1.36.4
|
version = 4.1.36.5
|
||||||
|
|
||||||
# main packages
|
# main packages
|
||||||
netty.version = 4.1.36.Final
|
netty.version = 4.1.36.Final
|
||||||
tcnative.version = 2.0.25.Final
|
tcnative.version = 2.0.25.Final
|
||||||
alpnagent.version = 2.0.9
|
|
||||||
|
|
||||||
# common
|
# common
|
||||||
xbib-net-url.version = 1.3.2
|
xbib-net-url.version = 1.3.2
|
||||||
|
@ -17,7 +16,6 @@ reactivestreams.version = 1.0.2
|
||||||
# server-rest
|
# server-rest
|
||||||
xbib-guice.version = 4.0.4
|
xbib-guice.version = 4.0.4
|
||||||
|
|
||||||
|
|
||||||
# test packages
|
# test packages
|
||||||
junit.version = 5.4.2
|
junit.version = 5.4.2
|
||||||
conscrypt.version = 2.0.0
|
conscrypt.version = 2.0.0
|
||||||
|
|
|
@ -4,9 +4,10 @@ import org.xbib.netty.http.common.cookie.Cookie;
|
||||||
import org.xbib.netty.http.common.cookie.CookieDecoder;
|
import org.xbib.netty.http.common.cookie.CookieDecoder;
|
||||||
import org.xbib.netty.http.common.cookie.CookieHeaderNames;
|
import org.xbib.netty.http.common.cookie.CookieHeaderNames;
|
||||||
import org.xbib.netty.http.common.cookie.DefaultCookie;
|
import org.xbib.netty.http.common.cookie.DefaultCookie;
|
||||||
import org.xbib.netty.http.common.util.TimeUtils;
|
import org.xbib.netty.http.common.util.DateTimeUtils;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,13 +20,12 @@ import java.util.Objects;
|
||||||
public final class ClientCookieDecoder extends CookieDecoder {
|
public final class ClientCookieDecoder extends CookieDecoder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strict encoder that validates that name and value chars are in the valid scope
|
* Strict encoder that validates that name and value chars are in the valid scope defined in RFC6265.
|
||||||
* defined in RFC6265
|
|
||||||
*/
|
*/
|
||||||
public static final ClientCookieDecoder STRICT = new ClientCookieDecoder(true);
|
public static final ClientCookieDecoder STRICT = new ClientCookieDecoder(true);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lax instance that doesn't validate name and value
|
* Lax instance that doesn't validate name and value.
|
||||||
*/
|
*/
|
||||||
public static final ClientCookieDecoder LAX = new ClientCookieDecoder(false);
|
public static final ClientCookieDecoder LAX = new ClientCookieDecoder(false);
|
||||||
|
|
||||||
|
@ -139,27 +139,13 @@ public final class ClientCookieDecoder extends CookieDecoder {
|
||||||
|
|
||||||
private boolean httpOnly;
|
private boolean httpOnly;
|
||||||
|
|
||||||
private String sameSite;
|
private Cookie.SameSite sameSite = Cookie.SameSite.STRICT;
|
||||||
|
|
||||||
CookieBuilder(DefaultCookie cookie, String header) {
|
CookieBuilder(DefaultCookie cookie, String header) {
|
||||||
this.cookie = cookie;
|
this.cookie = cookie;
|
||||||
this.header = header;
|
this.header = header;
|
||||||
}
|
}
|
||||||
|
|
||||||
private long mergeMaxAgeAndExpires() {
|
|
||||||
if (maxAge != Long.MIN_VALUE) {
|
|
||||||
return maxAge;
|
|
||||||
} else if (isValueDefined(expiresStart, expiresEnd)) {
|
|
||||||
Instant expiresDate = TimeUtils.parseDate(header); //DateFormatter.parseHttpDate(header, expiresStart, expiresEnd)
|
|
||||||
if (expiresDate != null) {
|
|
||||||
Instant now = Instant.now();
|
|
||||||
long maxAgeMillis = expiresDate.toEpochMilli() - now.toEpochMilli();
|
|
||||||
return maxAgeMillis / 1000 + (maxAgeMillis % 1000 != 0 ? 1 : 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Long.MIN_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
Cookie cookie() {
|
Cookie cookie() {
|
||||||
cookie.setDomain(domain);
|
cookie.setDomain(domain);
|
||||||
cookie.setPath(path);
|
cookie.setPath(path);
|
||||||
|
@ -218,6 +204,20 @@ public final class ClientCookieDecoder extends CookieDecoder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private long mergeMaxAgeAndExpires() {
|
||||||
|
if (maxAge != Long.MIN_VALUE) {
|
||||||
|
return maxAge;
|
||||||
|
} else if (isValueDefined(expiresStart, expiresEnd)) {
|
||||||
|
Instant expiresDate = DateTimeUtils.parseDate(header, expiresStart, expiresEnd);
|
||||||
|
if (expiresDate != null) {
|
||||||
|
Instant now = Instant.now();
|
||||||
|
long maxAgeMillis = expiresDate.toEpochMilli() - now.toEpochMilli();
|
||||||
|
return maxAgeMillis / 1000 + (maxAgeMillis % 1000 != 0 ? 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Long.MIN_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
private void parse7(int nameStart, int valueStart, int valueEnd) {
|
private void parse7(int nameStart, int valueStart, int valueEnd) {
|
||||||
if (header.regionMatches(true, nameStart, CookieHeaderNames.EXPIRES, 0, 7)) {
|
if (header.regionMatches(true, nameStart, CookieHeaderNames.EXPIRES, 0, 7)) {
|
||||||
expiresStart = valueStart;
|
expiresStart = valueStart;
|
||||||
|
@ -231,7 +231,10 @@ public final class ClientCookieDecoder extends CookieDecoder {
|
||||||
if (header.regionMatches(true, nameStart, CookieHeaderNames.HTTPONLY, 0, 8)) {
|
if (header.regionMatches(true, nameStart, CookieHeaderNames.HTTPONLY, 0, 8)) {
|
||||||
httpOnly = true;
|
httpOnly = true;
|
||||||
} else if (header.regionMatches(true, nameStart, CookieHeaderNames.SAMESITE, 0, 8)) {
|
} else if (header.regionMatches(true, nameStart, CookieHeaderNames.SAMESITE, 0, 8)) {
|
||||||
setSameSite(computeValue(valueStart, valueEnd));
|
String string = computeValue(valueStart, valueEnd);
|
||||||
|
if (string != null) {
|
||||||
|
setSameSite(Cookie.SameSite.valueOf(string.toUpperCase(Locale.ROOT)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,7 +246,7 @@ public final class ClientCookieDecoder extends CookieDecoder {
|
||||||
return isValueDefined(valueStart, valueEnd) ? header.substring(valueStart, valueEnd) : null;
|
return isValueDefined(valueStart, valueEnd) ? header.substring(valueStart, valueEnd) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSameSite(String value) {
|
private void setSameSite(Cookie.SameSite value) {
|
||||||
sameSite = value;
|
sameSite = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,7 +99,7 @@ public final class ClientCookieEncoder extends CookieEncoder {
|
||||||
* some cookies
|
* some cookies
|
||||||
* @return a Rfc6265 style Cookie header value, null if no cookies are passed.
|
* @return a Rfc6265 style Cookie header value, null if no cookies are passed.
|
||||||
*/
|
*/
|
||||||
public String encode(Cookie[] cookies) {
|
public String encode(Cookie... cookies) {
|
||||||
if (Objects.requireNonNull(cookies, "cookies").length == 0) {
|
if (Objects.requireNonNull(cookies, "cookies").length == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,15 +66,15 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel> {
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Http2MultiplexCodecBuilder clientMultiplexCodecBuilder = Http2MultiplexCodecBuilder.forClient(initializer)
|
Http2MultiplexCodecBuilder multiplexCodecBuilder = Http2MultiplexCodecBuilder.forClient(initializer)
|
||||||
.initialSettings(clientConfig.getHttp2Settings());
|
.initialSettings(clientConfig.getHttp2Settings());
|
||||||
if (clientConfig.isDebug()) {
|
if (clientConfig.isDebug()) {
|
||||||
clientMultiplexCodecBuilder.frameLogger(new PushPromiseHandler(LogLevel.DEBUG, "client"));
|
multiplexCodecBuilder.frameLogger(new PushPromiseHandler(LogLevel.DEBUG, "client"));
|
||||||
}
|
}
|
||||||
Http2MultiplexCodec http2MultiplexCodec = clientMultiplexCodecBuilder.build();
|
Http2MultiplexCodec multiplexCodec = multiplexCodecBuilder.autoAckSettingsFrame(true) .build();
|
||||||
ChannelPipeline p = ch.pipeline();
|
ChannelPipeline pipeline = ch.pipeline();
|
||||||
p.addLast("client-codec", http2MultiplexCodec);
|
pipeline.addLast("client-multiplex", multiplexCodec);
|
||||||
p.addLast("client-messages", new ClientMessages());
|
pipeline.addLast("client-messages", new ClientMessages());
|
||||||
}
|
}
|
||||||
|
|
||||||
class ClientMessages extends ChannelInboundHandlerAdapter {
|
class ClientMessages extends ChannelInboundHandlerAdapter {
|
||||||
|
|
|
@ -29,7 +29,6 @@ import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame;
|
||||||
import io.netty.handler.codec.http2.Http2DataFrame;
|
import io.netty.handler.codec.http2.Http2DataFrame;
|
||||||
import io.netty.handler.codec.http2.Http2Exception;
|
import io.netty.handler.codec.http2.Http2Exception;
|
||||||
import io.netty.handler.codec.http2.Http2Headers;
|
import io.netty.handler.codec.http2.Http2Headers;
|
||||||
import io.netty.handler.codec.http2.Http2MultiplexCodec;
|
|
||||||
import io.netty.handler.codec.http2.Http2HeadersFrame;
|
import io.netty.handler.codec.http2.Http2HeadersFrame;
|
||||||
import io.netty.handler.codec.http2.Http2StreamChannel;
|
import io.netty.handler.codec.http2.Http2StreamChannel;
|
||||||
import io.netty.handler.codec.http2.Http2StreamFrame;
|
import io.netty.handler.codec.http2.Http2StreamFrame;
|
||||||
|
@ -41,8 +40,7 @@ import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This handler converts from {@link Http2StreamFrame} to {@link HttpObject},
|
* This handler converts from {@link Http2StreamFrame} to {@link HttpObject},
|
||||||
* and back. It can be used as an adapter in conjunction with {@link
|
* and back. It can be used as an adapter to make http/2 connections backward-compatible with
|
||||||
* Http2MultiplexCodec} to make http/2 connections backward-compatible with
|
|
||||||
* {@link ChannelHandler}s expecting {@link HttpObject}.
|
* {@link ChannelHandler}s expecting {@link HttpObject}.
|
||||||
*
|
*
|
||||||
* For simplicity, it converts to chunked encoding unless the entire stream
|
* For simplicity, it converts to chunked encoding unless the entire stream
|
||||||
|
|
|
@ -135,10 +135,12 @@ abstract class BaseTransport implements Transport {
|
||||||
flow.get(key).get(value, timeUnit);
|
flow.get(key).get(value, timeUnit);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
String requestKey = getRequestKey(entry.getKey(), key);
|
String requestKey = getRequestKey(entry.getKey(), key);
|
||||||
|
if (requestKey != null) {
|
||||||
Request request = requests.get(requestKey);
|
Request request = requests.get(requestKey);
|
||||||
if (request != null && request.getCompletableFuture() != null) {
|
if (request != null && request.getCompletableFuture() != null) {
|
||||||
request.getCompletableFuture().completeExceptionally(e);
|
request.getCompletableFuture().completeExceptionally(e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
flow.fail(e);
|
flow.fail(e);
|
||||||
} finally {
|
} finally {
|
||||||
flow.remove(key);
|
flow.remove(key);
|
||||||
|
|
|
@ -0,0 +1,267 @@
|
||||||
|
package org.xbib.netty.http.client.test.cookie;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.xbib.netty.http.client.cookie.ClientCookieDecoder;
|
||||||
|
import org.xbib.netty.http.common.cookie.Cookie;
|
||||||
|
import org.xbib.netty.http.common.util.DateTimeUtils;
|
||||||
|
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class ClientCookieDecoderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodingSingleCookieV0() {
|
||||||
|
long millis = System.currentTimeMillis() + 50000;
|
||||||
|
String cookieString = "myCookie=myValue;expires=" +
|
||||||
|
DateTimeUtils.formatMillis(millis) +
|
||||||
|
";path=/apathsomewhere;domain=.adomainsomewhere;secure;";
|
||||||
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
|
||||||
|
assertNotNull(cookie);
|
||||||
|
assertEquals("myValue", cookie.value());
|
||||||
|
assertEquals(".adomainsomewhere", cookie.domain());
|
||||||
|
assertEquals("/apathsomewhere", cookie.path());
|
||||||
|
assertTrue(cookie.isSecure());
|
||||||
|
assertNotEquals(Long.MIN_VALUE, cookie.maxAge());
|
||||||
|
assertTrue(cookie.maxAge() >= 40 && cookie.maxAge() <= 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodingSingleCookieV0ExtraParamsIgnored() {
|
||||||
|
String cookieString = "myCookie=myValue;max-age=50;path=/apathsomewhere;" +
|
||||||
|
"domain=.adomainsomewhere;secure;comment=this is a comment;version=0;" +
|
||||||
|
"commentURL=http://aurl.com;port=\"80,8080\";discard;";
|
||||||
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
|
||||||
|
assertNotNull(cookie);
|
||||||
|
assertEquals("myValue", cookie.value());
|
||||||
|
assertEquals(".adomainsomewhere", cookie.domain());
|
||||||
|
assertEquals(50, cookie.maxAge());
|
||||||
|
assertEquals("/apathsomewhere", cookie.path());
|
||||||
|
assertTrue(cookie.isSecure());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodingSingleCookieV1() {
|
||||||
|
String cookieString = "myCookie=myValue;max-age=50;path=/apathsomewhere;domain=.adomainsomewhere" +
|
||||||
|
";secure;comment=this is a comment;version=1;";
|
||||||
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
|
||||||
|
assertEquals("myValue", cookie.value());
|
||||||
|
assertNotNull(cookie);
|
||||||
|
assertEquals(".adomainsomewhere", cookie.domain());
|
||||||
|
assertEquals(50, cookie.maxAge());
|
||||||
|
assertEquals("/apathsomewhere", cookie.path());
|
||||||
|
assertTrue(cookie.isSecure());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodingSingleCookieV1ExtraParamsIgnored() {
|
||||||
|
String cookieString = "myCookie=myValue;max-age=50;path=/apathsomewhere;" +
|
||||||
|
"domain=.adomainsomewhere;secure;comment=this is a comment;version=1;" +
|
||||||
|
"commentURL=http://aurl.com;port='80,8080';discard;";
|
||||||
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
|
||||||
|
assertNotNull(cookie);
|
||||||
|
assertEquals("myValue", cookie.value());
|
||||||
|
assertEquals(".adomainsomewhere", cookie.domain());
|
||||||
|
assertEquals(50, cookie.maxAge());
|
||||||
|
assertEquals("/apathsomewhere", cookie.path());
|
||||||
|
assertTrue(cookie.isSecure());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodingSingleCookieV2() {
|
||||||
|
String cookieString = "myCookie=myValue;max-age=50;path=/apathsomewhere;" +
|
||||||
|
"domain=.adomainsomewhere;secure;comment=this is a comment;version=2;" +
|
||||||
|
"commentURL=http://aurl.com;port=\"80,8080\";discard;";
|
||||||
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
|
||||||
|
assertNotNull(cookie);
|
||||||
|
assertEquals("myValue", cookie.value());
|
||||||
|
assertEquals(".adomainsomewhere", cookie.domain());
|
||||||
|
assertEquals(50, cookie.maxAge());
|
||||||
|
assertEquals("/apathsomewhere", cookie.path());
|
||||||
|
assertTrue(cookie.isSecure());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodingComplexCookie() {
|
||||||
|
String c1 = "myCookie=myValue;max-age=50;path=/apathsomewhere;" +
|
||||||
|
"domain=.adomainsomewhere;secure;comment=this is a comment;version=2;" +
|
||||||
|
"commentURL=\"http://aurl.com\";port='80,8080';discard;";
|
||||||
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(c1);
|
||||||
|
assertNotNull(cookie);
|
||||||
|
assertEquals("myValue", cookie.value());
|
||||||
|
assertEquals(".adomainsomewhere", cookie.domain());
|
||||||
|
assertEquals(50, cookie.maxAge());
|
||||||
|
assertEquals("/apathsomewhere", cookie.path());
|
||||||
|
assertTrue(cookie.isSecure());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodingQuotedCookie() {
|
||||||
|
Collection<String> sources = new ArrayList<>();
|
||||||
|
sources.add("a=\"\",");
|
||||||
|
sources.add("b=\"1\",");
|
||||||
|
Collection<Cookie> cookies = new ArrayList<>();
|
||||||
|
for (String source : sources) {
|
||||||
|
cookies.add(ClientCookieDecoder.STRICT.decode(source));
|
||||||
|
}
|
||||||
|
Iterator<Cookie> it = cookies.iterator();
|
||||||
|
Cookie c;
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("a", c.name());
|
||||||
|
assertEquals("", c.value());
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("b", c.name());
|
||||||
|
assertEquals("1", c.value());
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodingGoogleAnalyticsCookie() {
|
||||||
|
String source = "ARPT=LWUKQPSWRTUN04CKKJI; " +
|
||||||
|
"kw-2E343B92-B097-442c-BFA5-BE371E0325A2=unfinished furniture; " +
|
||||||
|
"__utma=48461872.1094088325.1258140131.1258140131.1258140131.1; " +
|
||||||
|
"__utmb=48461872.13.10.1258140131; __utmc=48461872; " +
|
||||||
|
"__utmz=48461872.1258140131.1.1.utmcsr=overstock.com|utmccn=(referral)|" +
|
||||||
|
"utmcmd=referral|utmcct=/Home-Garden/Furniture/Clearance,/clearance,/32/dept.html";
|
||||||
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(source);
|
||||||
|
assertEquals("ARPT", cookie.name());
|
||||||
|
assertEquals("LWUKQPSWRTUN04CKKJI", cookie.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodingLongDates() {
|
||||||
|
ZonedDateTime zonedDateTime = ZonedDateTime.of(2100,12,31,23,59,59,0, ZoneId.of("UTC"));
|
||||||
|
long expectedMaxAge = ((zonedDateTime.toEpochSecond() * 1000L) - System.currentTimeMillis()) / 1000;
|
||||||
|
String source = "Format=EU; expires=Fri, 31-Dec-2100 23:59:59 GMT; path=/";
|
||||||
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(source);
|
||||||
|
assertTrue(Math.abs(expectedMaxAge - cookie.maxAge()) < 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodingValueWithCommaFails() {
|
||||||
|
String source = "UserCookie=timeZoneName=(GMT+04:00) Moscow, St. Petersburg, Volgograd&promocode=®ion=BE;" +
|
||||||
|
" expires=Sat, 01-Dec-2012 10:53:31 GMT; path=/";
|
||||||
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(source);
|
||||||
|
assertNull(cookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodingWeirdNames1() {
|
||||||
|
String src = "path=; expires=Mon, 01-Jan-1990 00:00:00 GMT; path=/; domain=.www.google.com";
|
||||||
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(src);
|
||||||
|
assertEquals("path", cookie.name());
|
||||||
|
assertEquals("", cookie.value());
|
||||||
|
assertEquals("/", cookie.path());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodingWeirdNames2() {
|
||||||
|
String src = "HTTPOnly=";
|
||||||
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(src);
|
||||||
|
assertEquals("HTTPOnly", cookie.name());
|
||||||
|
assertEquals("", cookie.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodingValuesWithCommasAndEqualsFails() {
|
||||||
|
String src = "A=v=1&lg=en-US,it-IT,it&intl=it&np=1;T=z=E";
|
||||||
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(src);
|
||||||
|
assertNull(cookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodingLongValue() {
|
||||||
|
String longValue = "b___$Q__$ha__<NC=MN(F__%#4__<NC=MN(F__2_d____#=IvZB__2_F____'=KqtH__2-9____" +
|
||||||
|
"'=IvZM__3f:____$=HbQW__3g'____%=J^wI__3g-____%=J^wI__3g1____$=HbQW__3g2____" +
|
||||||
|
"$=HbQW__3g5____%=J^wI__3g9____$=HbQW__3gT____$=HbQW__3gX____#=J^wI__3gY____" +
|
||||||
|
"#=J^wI__3gh____$=HbQW__3gj____$=HbQW__3gr____$=HbQW__3gx____#=J^wI__3h_____" +
|
||||||
|
"$=HbQW__3h$____#=J^wI__3h'____$=HbQW__3h_____$=HbQW__3h0____%=J^wI__3h1____" +
|
||||||
|
"#=J^wI__3h2____$=HbQW__3h4____$=HbQW__3h7____$=HbQW__3h8____%=J^wI__3h:____" +
|
||||||
|
"#=J^wI__3h@____%=J^wI__3hB____$=HbQW__3hC____$=HbQW__3hL____$=HbQW__3hQ____" +
|
||||||
|
"$=HbQW__3hS____%=J^wI__3hU____$=HbQW__3h[____$=HbQW__3h^____$=HbQW__3hd____" +
|
||||||
|
"%=J^wI__3he____%=J^wI__3hf____%=J^wI__3hg____$=HbQW__3hh____%=J^wI__3hi____" +
|
||||||
|
"%=J^wI__3hv____$=HbQW__3i/____#=J^wI__3i2____#=J^wI__3i3____%=J^wI__3i4____" +
|
||||||
|
"$=HbQW__3i7____$=HbQW__3i8____$=HbQW__3i9____%=J^wI__3i=____#=J^wI__3i>____" +
|
||||||
|
"%=J^wI__3iD____$=HbQW__3iF____#=J^wI__3iH____%=J^wI__3iM____%=J^wI__3iS____" +
|
||||||
|
"#=J^wI__3iU____%=J^wI__3iZ____#=J^wI__3i]____%=J^wI__3ig____%=J^wI__3ij____" +
|
||||||
|
"%=J^wI__3ik____#=J^wI__3il____$=HbQW__3in____%=J^wI__3ip____$=HbQW__3iq____" +
|
||||||
|
"$=HbQW__3it____%=J^wI__3ix____#=J^wI__3j_____$=HbQW__3j%____$=HbQW__3j'____" +
|
||||||
|
"%=J^wI__3j(____%=J^wI__9mJ____'=KqtH__=SE__<NC=MN(F__?VS__<NC=MN(F__Zw`____" +
|
||||||
|
"%=KqtH__j+C__<NC=MN(F__j+M__<NC=MN(F__j+a__<NC=MN(F__j_.__<NC=MN(F__n>M____" +
|
||||||
|
"'=KqtH__s1X____$=MMyc__s1_____#=MN#O__ypn____'=KqtH__ypr____'=KqtH_#%h_____" +
|
||||||
|
"%=KqtH_#%o_____'=KqtH_#)H6__<NC=MN(F_#*%'____%=KqtH_#+k(____'=KqtH_#-E_____" +
|
||||||
|
"'=KqtH_#1)w____'=KqtH_#1)y____'=KqtH_#1*M____#=KqtH_#1*p____'=KqtH_#14Q__<N" +
|
||||||
|
"C=MN(F_#14S__<NC=MN(F_#16I__<NC=MN(F_#16N__<NC=MN(F_#16X__<NC=MN(F_#16k__<N" +
|
||||||
|
"C=MN(F_#17@__<NC=MN(F_#17A__<NC=MN(F_#1Cq____'=KqtH_#7)_____#=KqtH_#7)b____" +
|
||||||
|
"#=KqtH_#7Ww____'=KqtH_#?cQ____'=KqtH_#His____'=KqtH_#Jrh____'=KqtH_#O@M__<N" +
|
||||||
|
"C=MN(F_#O@O__<NC=MN(F_#OC6__<NC=MN(F_#Os.____#=KqtH_#YOW____#=H/Li_#Zat____" +
|
||||||
|
"'=KqtH_#ZbI____%=KqtH_#Zbc____'=KqtH_#Zbs____%=KqtH_#Zby____'=KqtH_#Zce____" +
|
||||||
|
"'=KqtH_#Zdc____%=KqtH_#Zea____'=KqtH_#ZhI____#=KqtH_#ZiD____'=KqtH_#Zis____" +
|
||||||
|
"'=KqtH_#Zj0____#=KqtH_#Zj1____'=KqtH_#Zj[____'=KqtH_#Zj]____'=KqtH_#Zj^____" +
|
||||||
|
"'=KqtH_#Zjb____'=KqtH_#Zk_____'=KqtH_#Zk6____#=KqtH_#Zk9____%=KqtH_#Zk<____" +
|
||||||
|
"'=KqtH_#Zl>____'=KqtH_#]9R____$=H/Lt_#]I6____#=KqtH_#]Z#____%=KqtH_#^*N____" +
|
||||||
|
"#=KqtH_#^:m____#=KqtH_#_*_____%=J^wI_#`-7____#=KqtH_#`T>____'=KqtH_#`T?____" +
|
||||||
|
"'=KqtH_#`TA____'=KqtH_#`TB____'=KqtH_#`TG____'=KqtH_#`TP____#=KqtH_#`U_____" +
|
||||||
|
"'=KqtH_#`U/____'=KqtH_#`U0____#=KqtH_#`U9____'=KqtH_#aEQ____%=KqtH_#b<)____" +
|
||||||
|
"'=KqtH_#c9-____%=KqtH_#dxC____%=KqtH_#dxE____%=KqtH_#ev$____'=KqtH_#fBi____" +
|
||||||
|
"#=KqtH_#fBj____'=KqtH_#fG)____'=KqtH_#fG+____'=KqtH_#g<d____'=KqtH_#g<e____" +
|
||||||
|
"'=KqtH_#g=J____'=KqtH_#gat____#=KqtH_#s`D____#=J_#p_#sg?____#=J_#p_#t<a____" +
|
||||||
|
"#=KqtH_#t<c____#=KqtH_#trY____$=JiYj_#vA$____'=KqtH_#xs_____'=KqtH_$$rO____" +
|
||||||
|
"#=KqtH_$$rP____#=KqtH_$(_%____'=KqtH_$)]o____%=KqtH_$_@)____'=KqtH_$_k]____" +
|
||||||
|
"'=KqtH_$1]+____%=KqtH_$3IO____%=KqtH_$3J#____'=KqtH_$3J.____'=KqtH_$3J:____" +
|
||||||
|
"#=KqtH_$3JH____#=KqtH_$3JI____#=KqtH_$3JK____%=KqtH_$3JL____'=KqtH_$3JS____" +
|
||||||
|
"'=KqtH_$8+M____#=KqtH_$99d____%=KqtH_$:Lw____#=LK+x_$:N@____#=KqtG_$:NC____" +
|
||||||
|
"#=KqtG_$:hW____'=KqtH_$:i[____'=KqtH_$:ih____'=KqtH_$:it____'=KqtH_$:kO____" +
|
||||||
|
"'=KqtH_$>*B____'=KqtH_$>hD____+=J^x0_$?lW____'=KqtH_$?ll____'=KqtH_$?lm____" +
|
||||||
|
"%=KqtH_$?mi____'=KqtH_$?mx____'=KqtH_$D7]____#=J_#p_$D@T____#=J_#p_$V<g____" +
|
||||||
|
"'=KqtH";
|
||||||
|
Cookie cookie = ClientCookieDecoder.STRICT.decode("bh=\"" + longValue + "\";");
|
||||||
|
assertEquals("bh", cookie.name());
|
||||||
|
assertEquals(longValue, cookie.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testIgnoreEmptyDomain() {
|
||||||
|
String emptyDomain = "sessionid=OTY4ZDllNTgtYjU3OC00MWRjLTkzMWMtNGUwNzk4MTY0MTUw;Domain=;Path=/";
|
||||||
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(emptyDomain);
|
||||||
|
assertNull(cookie.domain());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testIgnoreEmptyPath() {
|
||||||
|
String emptyPath = "sessionid=OTY4ZDllNTgtYjU3OC00MWRjLTkzMWMtNGUwNzk4MTY0MTUw;Domain=;Path=";
|
||||||
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(emptyPath);
|
||||||
|
assertNull(cookie.path());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSameSiteStrict() {
|
||||||
|
String sameSite = "sessionid=OTY4ZDllNTgtYjU3OC00MWRjLTkzMWMtNGUwNzk4MTY0MTUw;SameSite=Strict";
|
||||||
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(sameSite);
|
||||||
|
assertEquals(Cookie.SameSite.STRICT, cookie.sameSite());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSameSiteLax() {
|
||||||
|
String sameSite = "sessionid=OTY4ZDllNTgtYjU3OC00MWRjLTkzMWMtNGUwNzk4MTY0MTUw;SameSite=Lax";
|
||||||
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(sameSite);
|
||||||
|
assertEquals(Cookie.SameSite.LAX, cookie.sameSite());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEmptySameSite() {
|
||||||
|
String sameSite = "sessionid=OTY4ZDllNTgtYjU3OC00MWRjLTkzMWMtNGUwNzk4MTY0MTUw;SameSite=";
|
||||||
|
Cookie cookie = ClientCookieDecoder.STRICT.decode(sameSite);
|
||||||
|
assertEquals(Cookie.SameSite.STRICT, cookie.sameSite());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package org.xbib.netty.http.client.test.cookie;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.xbib.netty.http.client.cookie.ClientCookieEncoder;
|
||||||
|
import org.xbib.netty.http.common.cookie.Cookie;
|
||||||
|
import org.xbib.netty.http.common.cookie.DefaultCookie;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
class ClientCookieEncoderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEncodingMultipleClientCookies() {
|
||||||
|
String c1 = "myCookie=myValue";
|
||||||
|
String c2 = "myCookie2=myValue2";
|
||||||
|
String c3 = "myCookie3=myValue3";
|
||||||
|
Cookie cookie1 = new DefaultCookie("myCookie", "myValue");
|
||||||
|
cookie1.setDomain(".adomainsomewhere");
|
||||||
|
cookie1.setMaxAge(50);
|
||||||
|
cookie1.setPath("/apathsomewhere");
|
||||||
|
cookie1.setSecure(true);
|
||||||
|
Cookie cookie2 = new DefaultCookie("myCookie2", "myValue2");
|
||||||
|
cookie2.setDomain(".anotherdomainsomewhere");
|
||||||
|
cookie2.setPath("/anotherpathsomewhere");
|
||||||
|
cookie2.setSecure(false);
|
||||||
|
Cookie cookie3 = new DefaultCookie("myCookie3", "myValue3");
|
||||||
|
String encodedCookie = ClientCookieEncoder.STRICT.encode(cookie1, cookie2, cookie3);
|
||||||
|
// Cookies should be sorted into decreasing order of path length, as per RFC6265.
|
||||||
|
// When no path is provided, we assume maximum path length (so cookie3 comes first).
|
||||||
|
assertEquals(c3 + "; " + c2 + "; " + c1, encodedCookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testWrappedCookieValue() {
|
||||||
|
ClientCookieEncoder.STRICT.encode(new DefaultCookie("myCookie", "\"foo\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRejectCookieValueWithSemicolon() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> {
|
||||||
|
ClientCookieEncoder.STRICT.encode(new DefaultCookie("myCookie", "foo;bar"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ import java.net.InetSocketAddress;
|
||||||
*/
|
*/
|
||||||
public class HttpAddress implements PoolKey {
|
public class HttpAddress implements PoolKey {
|
||||||
|
|
||||||
private static final HttpVersion HTTP_2_0 = HttpVersion.valueOf("HTTP/2.0");
|
public static final HttpVersion HTTP_2_0 = HttpVersion.valueOf("HTTP/2.0");
|
||||||
|
|
||||||
private final String host;
|
private final String host;
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ public class HttpAddress implements PoolKey {
|
||||||
|
|
||||||
private InetSocketAddress inetSocketAddress;
|
private InetSocketAddress inetSocketAddress;
|
||||||
|
|
||||||
|
|
||||||
public static HttpAddress http1(String host) {
|
public static HttpAddress http1(String host) {
|
||||||
return new HttpAddress(host, 80, HttpVersion.HTTP_1_1, false);
|
return new HttpAddress(host, 80, HttpVersion.HTTP_1_1, false);
|
||||||
}
|
}
|
||||||
|
@ -86,9 +85,10 @@ public class HttpAddress implements PoolKey {
|
||||||
this.secure = secure;
|
this.secure = secure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public InetSocketAddress getInetSocketAddress() {
|
public InetSocketAddress getInetSocketAddress() {
|
||||||
if (inetSocketAddress == null) {
|
if (inetSocketAddress == null) {
|
||||||
// this may execute DNS lookup
|
// this may execute a DNS lookup, cache the result here
|
||||||
this.inetSocketAddress = new InetSocketAddress(host, port);
|
this.inetSocketAddress = new InetSocketAddress(host, port);
|
||||||
}
|
}
|
||||||
return inetSocketAddress;
|
return inetSocketAddress;
|
||||||
|
@ -106,6 +106,7 @@ public class HttpAddress implements PoolKey {
|
||||||
return secure;
|
return secure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return host + ":" + port + " (version:" + version + ",secure:" + secure + ")";
|
return host + ":" + port + " (version:" + version + ",secure:" + secure + ")";
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ package org.xbib.netty.http.common;
|
||||||
import org.xbib.net.PercentDecoder;
|
import org.xbib.net.PercentDecoder;
|
||||||
import org.xbib.net.PercentEncoder;
|
import org.xbib.net.PercentEncoder;
|
||||||
import org.xbib.net.PercentEncoders;
|
import org.xbib.net.PercentEncoders;
|
||||||
import org.xbib.netty.http.common.util.LimitedSortedStringSet;
|
import org.xbib.netty.http.common.util.LimitedSet;
|
||||||
import org.xbib.netty.http.common.util.LimitedStringMap;
|
import org.xbib.netty.http.common.util.LimitedMap;
|
||||||
|
|
||||||
import java.nio.charset.MalformedInputException;
|
import java.nio.charset.MalformedInputException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -42,7 +42,7 @@ public class HttpParameters implements Map<String, SortedSet<String>> {
|
||||||
|
|
||||||
private final int elementSizeLimit;
|
private final int elementSizeLimit;
|
||||||
|
|
||||||
private final LimitedStringMap map;
|
private final LimitedMap<String, String> map;
|
||||||
|
|
||||||
private final PercentEncoder percentEncoder;
|
private final PercentEncoder percentEncoder;
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ public class HttpParameters implements Map<String, SortedSet<String>> {
|
||||||
this.maxParam = maxParam;
|
this.maxParam = maxParam;
|
||||||
this.sizeLimit = sizeLimit;
|
this.sizeLimit = sizeLimit;
|
||||||
this.elementSizeLimit = elementSizeLimit;
|
this.elementSizeLimit = elementSizeLimit;
|
||||||
this.map = new LimitedStringMap(maxParam);
|
this.map = new LimitedMap<>(maxParam);
|
||||||
this.percentEncoder = PercentEncoders.getQueryEncoder(StandardCharsets.UTF_8);
|
this.percentEncoder = PercentEncoders.getQueryEncoder(StandardCharsets.UTF_8);
|
||||||
this.percentDecoder = new PercentDecoder();
|
this.percentDecoder = new PercentDecoder();
|
||||||
this.contentType = contentType;
|
this.contentType = contentType;
|
||||||
|
@ -183,7 +183,7 @@ public class HttpParameters implements Map<String, SortedSet<String>> {
|
||||||
String k = percentEncode ? percentEncoder.encode(key) : key;
|
String k = percentEncode ? percentEncoder.encode(key) : key;
|
||||||
SortedSet<String> values = map.get(k);
|
SortedSet<String> values = map.get(k);
|
||||||
if (values == null) {
|
if (values == null) {
|
||||||
values = new LimitedSortedStringSet(sizeLimit, elementSizeLimit);
|
values = new LimitedSet<>(sizeLimit, elementSizeLimit);
|
||||||
map.put(k, values);
|
map.put(k, values);
|
||||||
}
|
}
|
||||||
String v = null;
|
String v = null;
|
||||||
|
@ -236,7 +236,7 @@ public class HttpParameters implements Map<String, SortedSet<String>> {
|
||||||
for (String key : m.keySet()) {
|
for (String key : m.keySet()) {
|
||||||
SortedSet<String> vals = get(key);
|
SortedSet<String> vals = get(key);
|
||||||
if (vals == null) {
|
if (vals == null) {
|
||||||
vals = new LimitedSortedStringSet(sizeLimit, elementSizeLimit);
|
vals = new LimitedSet<>(sizeLimit, elementSizeLimit);
|
||||||
put(key, vals);
|
put(key, vals);
|
||||||
}
|
}
|
||||||
vals.addAll(m.get(key));
|
vals.addAll(m.get(key));
|
||||||
|
|
|
@ -124,7 +124,29 @@ public interface Cookie extends Comparable<Cookie> {
|
||||||
*/
|
*/
|
||||||
void setHttpOnly(boolean httpOnly);
|
void setHttpOnly(boolean httpOnly);
|
||||||
|
|
||||||
String sameSite();
|
/**
|
||||||
|
* Checks to see if this {@link Cookie} is valid on same site.
|
||||||
|
*
|
||||||
|
* {@code SameSite.STRICT} being the default mode/
|
||||||
|
*
|
||||||
|
* @return the same site value
|
||||||
|
*/
|
||||||
|
SameSite sameSite();
|
||||||
|
|
||||||
void setSameSite(String sameSite);
|
/**
|
||||||
|
* Determines if this {@link Cookie} is same site.
|
||||||
|
* If set to {@code SameSite.STRICT},
|
||||||
|
*
|
||||||
|
* {@code SameSite.LAX} mode is adding one exception for the cookie to be sent if we’re not in a Same-Site context.
|
||||||
|
* The defined {@link Cookie} will also be sent for requests using a safe method (GET method for most)
|
||||||
|
* for top-level navigation, basically something resulting in the URL changing in the web browser address bar.
|
||||||
|
*
|
||||||
|
* {@code SameSite.STRICT} mode would prevent any session cookie to be sent for a website reached by following
|
||||||
|
* an external link (from an email, from search engines results, etc.), resulting for a user not being logged in.
|
||||||
|
*
|
||||||
|
* @param sameSite the same site value
|
||||||
|
*/
|
||||||
|
void setSameSite(SameSite sameSite);
|
||||||
|
|
||||||
|
enum SameSite { STRICT, LAX }
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ public class DefaultCookie implements Cookie {
|
||||||
|
|
||||||
private boolean httpOnly;
|
private boolean httpOnly;
|
||||||
|
|
||||||
private String sameSite;
|
private SameSite sameSite;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new cookie with the specified name and value.
|
* Creates a new cookie with the specified name and value.
|
||||||
|
@ -38,6 +38,10 @@ public class DefaultCookie implements Cookie {
|
||||||
setValue(value);
|
setValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DefaultCookie(String name, Payload payload) {
|
||||||
|
this(name, payload.toString());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String name() {
|
public String name() {
|
||||||
return name;
|
return name;
|
||||||
|
@ -114,12 +118,12 @@ public class DefaultCookie implements Cookie {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSameSite(String sameSite) {
|
public void setSameSite(SameSite sameSite) {
|
||||||
this.sameSite = sameSite;
|
this.sameSite = sameSite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String sameSite() {
|
public SameSite sameSite() {
|
||||||
return sameSite;
|
return sameSite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +165,7 @@ public class DefaultCookie implements Cookie {
|
||||||
} else if (that.sameSite() == null) {
|
} else if (that.sameSite() == null) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return sameSite().equalsIgnoreCase(that.sameSite());
|
return sameSite().name().equalsIgnoreCase(that.sameSite().name());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,7 +220,7 @@ public class DefaultCookie implements Cookie {
|
||||||
buf.append(", HTTPOnly");
|
buf.append(", HTTPOnly");
|
||||||
}
|
}
|
||||||
if (sameSite() != null) {
|
if (sameSite() != null) {
|
||||||
buf.append(", SameSite=").append(sameSite());
|
buf.append(", SameSite=").append(sameSite().name());
|
||||||
}
|
}
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
package org.xbib.netty.http.common.cookie;
|
||||||
|
|
||||||
|
import org.xbib.net.PercentDecoder;
|
||||||
|
import org.xbib.net.PercentEncoder;
|
||||||
|
import org.xbib.net.PercentEncoders;
|
||||||
|
import org.xbib.netty.http.common.util.Codec;
|
||||||
|
import org.xbib.netty.http.common.util.CryptUtils;
|
||||||
|
import org.xbib.netty.http.common.util.HMac;
|
||||||
|
|
||||||
|
import java.nio.charset.MalformedInputException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.charset.UnmappableCharacterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
|
||||||
|
public class Payload {
|
||||||
|
|
||||||
|
private static final PercentEncoder PERCENT_ENCODER = PercentEncoders.getCookieEncoder(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
private static final PercentDecoder PERCENT_DECODER = new PercentDecoder(StandardCharsets.UTF_8.newDecoder());
|
||||||
|
|
||||||
|
private final Codec codec;
|
||||||
|
|
||||||
|
private final HMac hmac;
|
||||||
|
|
||||||
|
private final String publicValue;
|
||||||
|
|
||||||
|
private final String privateValue;
|
||||||
|
|
||||||
|
private final String secret;
|
||||||
|
|
||||||
|
public Payload(Codec codec, HMac hmac, String publicValue, String privateValue, String secret) {
|
||||||
|
this.codec = codec;
|
||||||
|
this.hmac = hmac;
|
||||||
|
this.publicValue = publicValue;
|
||||||
|
this.privateValue = privateValue;
|
||||||
|
this.secret = secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Payload(Codec codec, HMac hmac, String rawValue, String secret)
|
||||||
|
throws MalformedInputException, UnmappableCharacterException, InvalidKeyException,
|
||||||
|
NoSuchAlgorithmException, SignatureException {
|
||||||
|
this.codec = codec;
|
||||||
|
this.hmac = hmac;
|
||||||
|
String[] s = PERCENT_DECODER.decode(rawValue).split(":", 3);
|
||||||
|
if (s.length != 3) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
this.publicValue = s[0];
|
||||||
|
this.privateValue = s[1];
|
||||||
|
this.secret = secret;
|
||||||
|
if (!s[2].equals(CryptUtils.hmac(codec, privateValue, secret, hmac))) {
|
||||||
|
throw new SignatureException("HMAC signature does not match");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPublicValue() {
|
||||||
|
return publicValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPrivateValue() {
|
||||||
|
return privateValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSecret() {
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
try {
|
||||||
|
return PERCENT_ENCODER.encode(String.join(":", publicValue, privateValue,
|
||||||
|
CryptUtils.hmac(codec, privateValue, secret, hmac)));
|
||||||
|
} catch (NoSuchAlgorithmException | InvalidKeyException | MalformedInputException | UnmappableCharacterException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.xbib.netty.http.common.util;
|
||||||
|
|
||||||
|
public enum Algo {
|
||||||
|
MD5("MD5", "md5"),
|
||||||
|
SHA("SHA","sha"),
|
||||||
|
SHA256("SHA-256","sha256"),
|
||||||
|
SHA512("SHA-512", "sha512"),
|
||||||
|
SSHA("SHA1", "ssha"),
|
||||||
|
SSHA256("SHA-256", "ssha"),
|
||||||
|
SSHA512("SHA-512", "ssha");
|
||||||
|
|
||||||
|
String algo;
|
||||||
|
|
||||||
|
String prefix;
|
||||||
|
|
||||||
|
Algo(String algo, String prefix) {
|
||||||
|
this.algo = algo;
|
||||||
|
this.prefix = prefix;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package org.xbib.netty.http.common.util;
|
||||||
|
|
||||||
|
public enum Codec {
|
||||||
|
BASE64, HEX
|
||||||
|
}
|
|
@ -0,0 +1,150 @@
|
||||||
|
package org.xbib.netty.http.common.util;
|
||||||
|
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility class for invoking encryption methods and returning password strings,
|
||||||
|
* using {@link java.security.MessageDigest} and {@link javax.crypto.Mac}.
|
||||||
|
*/
|
||||||
|
public class CryptUtils {
|
||||||
|
|
||||||
|
private static final Random random = new SecureRandom();
|
||||||
|
|
||||||
|
public static String randomHex(int length) {
|
||||||
|
byte[] b = new byte[length];
|
||||||
|
random.nextBytes(b);
|
||||||
|
return encodeHex(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String md5(String plainText) throws NoSuchAlgorithmException {
|
||||||
|
return digest(Codec.BASE64, plainText.getBytes(StandardCharsets.UTF_8), null, Algo.MD5.algo, Algo.MD5.prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String sha(String plainText) throws NoSuchAlgorithmException {
|
||||||
|
return digest(Codec.BASE64, plainText.getBytes(StandardCharsets.UTF_8), null, Algo.SHA.algo, Algo.SHA.prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String sha256(String plainText) throws NoSuchAlgorithmException {
|
||||||
|
return digest(Codec.BASE64, plainText.getBytes(StandardCharsets.UTF_8), null, Algo.SHA256.algo, Algo.SHA256.prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String sha512(String plainText) throws NoSuchAlgorithmException {
|
||||||
|
return digest(Codec.BASE64, plainText.getBytes(StandardCharsets.UTF_8), null, Algo.SHA512.algo, Algo.SHA512.prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String ssha(String plainText, byte[] salt) throws NoSuchAlgorithmException {
|
||||||
|
return digest(Codec.BASE64, plainText.getBytes(StandardCharsets.UTF_8), salt, Algo.SSHA.algo, Algo.SSHA.prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String ssha256(String plainText, byte[] salt) throws NoSuchAlgorithmException {
|
||||||
|
return digest(Codec.BASE64, plainText.getBytes(StandardCharsets.UTF_8), salt, Algo.SSHA256.algo, Algo.SSHA256.prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String ssha512(String plainText, byte[] salt) throws NoSuchAlgorithmException {
|
||||||
|
return digest(Codec.BASE64, plainText.getBytes(StandardCharsets.UTF_8), salt, Algo.SSHA512.algo, Algo.SSHA512.prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String hmacSHA1(String plainText, String secret) throws NoSuchAlgorithmException, InvalidKeyException {
|
||||||
|
return hmac(Codec.BASE64, plainText.getBytes(StandardCharsets.UTF_8), secret.getBytes(StandardCharsets.UTF_8), HMac.HMAC_SHA1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String hmacSHA1(byte[] plainText, String secret) throws InvalidKeyException, NoSuchAlgorithmException {
|
||||||
|
return hmac(Codec.BASE64, plainText, secret.getBytes(StandardCharsets.UTF_8), HMac.HMAC_SHA1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String hmacSHA1(byte[] plainText, byte[] secret) throws InvalidKeyException, NoSuchAlgorithmException {
|
||||||
|
return hmac(Codec.BASE64, plainText, secret, HMac.HMAC_SHA1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String hmacSHA256(String plainText, String secret) throws NoSuchAlgorithmException, InvalidKeyException {
|
||||||
|
return hmac(Codec.BASE64, plainText.getBytes(StandardCharsets.UTF_8), secret.getBytes(StandardCharsets.UTF_8), HMac.HMAC_SHA256);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String hmacSHA256(byte[] plainText, String secret) throws InvalidKeyException, NoSuchAlgorithmException {
|
||||||
|
return hmac(Codec.BASE64, plainText, secret.getBytes(StandardCharsets.UTF_8), HMac.HMAC_SHA256);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String hmacSHA256(byte[] plainText, byte[] secret) throws InvalidKeyException, NoSuchAlgorithmException {
|
||||||
|
return hmac(Codec.BASE64, plainText, secret, HMac.HMAC_SHA256);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String hmac(Codec codec, String plainText, String secret, HMac hmac) throws InvalidKeyException, NoSuchAlgorithmException {
|
||||||
|
return hmac(codec, plainText.getBytes(StandardCharsets.UTF_8), secret.getBytes(StandardCharsets.UTF_8), hmac);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String digest(Codec codec, byte[] plainText, byte[] salt, String algo, String prefix) throws NoSuchAlgorithmException {
|
||||||
|
Objects.requireNonNull(plainText);
|
||||||
|
MessageDigest digest = MessageDigest.getInstance(algo);
|
||||||
|
digest.update(plainText);
|
||||||
|
byte[] bytes = digest.digest();
|
||||||
|
if (salt != null) {
|
||||||
|
digest.update(salt);
|
||||||
|
byte[] hash = digest.digest();
|
||||||
|
bytes = new byte[salt.length + hash.length];
|
||||||
|
System.arraycopy(hash, 0, bytes, 0, hash.length);
|
||||||
|
System.arraycopy(salt, 0, bytes, hash.length, salt.length);
|
||||||
|
}
|
||||||
|
return '{' + prefix + '}' +
|
||||||
|
(codec == Codec.BASE64 ? Base64.getEncoder().encodeToString(bytes) :
|
||||||
|
codec == Codec.HEX ? encodeHex(bytes) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String hmac(Codec codec, byte[] plainText, byte[] secret, HMac hmac) throws NoSuchAlgorithmException, InvalidKeyException {
|
||||||
|
Objects.requireNonNull(plainText);
|
||||||
|
Objects.requireNonNull(secret);
|
||||||
|
Mac mac = Mac.getInstance(hmac.algo);
|
||||||
|
SecretKeySpec secretKeySpec = new SecretKeySpec(secret, hmac.algo);
|
||||||
|
mac.init(secretKeySpec);
|
||||||
|
return codec == Codec.BASE64 ? Base64.getEncoder().encodeToString(mac.doFinal(plainText)) :
|
||||||
|
codec == Codec.HEX ? encodeHex(mac.doFinal(plainText)) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String encodeHex(byte[] bytes) {
|
||||||
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
for (byte b: bytes) {
|
||||||
|
stringBuilder.append(Integer.toHexString((int) b & 0xFF));
|
||||||
|
}
|
||||||
|
return stringBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes the hex-encoded bytes and returns their value a byte string.
|
||||||
|
* @param hex hexidecimal code
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static byte[] decodeHex(String hex) {
|
||||||
|
Objects.requireNonNull(hex);
|
||||||
|
if (hex.length() % 2 != 0) {
|
||||||
|
throw new IllegalArgumentException("unexpected hex string " + hex);
|
||||||
|
}
|
||||||
|
byte[] result = new byte[hex.length() / 2];
|
||||||
|
for (int i = 0; i < result.length; i++) {
|
||||||
|
int d1 = decodeHexDigit(hex.charAt(i * 2)) << 4;
|
||||||
|
int d2 = decodeHexDigit(hex.charAt(i * 2 + 1));
|
||||||
|
result[i] = (byte) (d1 + d2);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int decodeHexDigit(char c) {
|
||||||
|
if (c >= '0' && c <= '9') {
|
||||||
|
return c - '0';
|
||||||
|
}
|
||||||
|
if (c >= 'a' && c <= 'f') {
|
||||||
|
return c - 'a' + 10;
|
||||||
|
}
|
||||||
|
if (c >= 'A' && c <= 'F') {
|
||||||
|
return c - 'A' + 10;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("unexpected hex digit " + c);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package org.xbib.netty.http.common.util;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class DateTimeUtils {
|
||||||
|
|
||||||
|
private static final ZoneId ZONE_UTC = ZoneId.of("UTC");
|
||||||
|
|
||||||
|
private static final Locale ROOT_LOCALE = Locale.ROOT;
|
||||||
|
|
||||||
|
private static final String RFC1036_PATTERN = "EEE, dd-MMM-yyyy HH:mm:ss zzz";
|
||||||
|
|
||||||
|
private static final String ASCIITIME_PATTERN = "EEE MMM d HH:mm:ss yyyyy";
|
||||||
|
|
||||||
|
public static String formatInstant(Instant instant) {
|
||||||
|
return DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.ofInstant(instant, ZoneOffset.UTC));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatMillis(long millis) {
|
||||||
|
return formatInstant(Instant.ofEpochMilli(millis));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatSeconds(long seconds) {
|
||||||
|
return formatInstant(Instant.now().plusSeconds(seconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC 2616 allows RFC 1123, RFC 1036, ASCII time
|
||||||
|
private static final DateTimeFormatter[] dateTimeFormatters = {
|
||||||
|
DateTimeFormatter.RFC_1123_DATE_TIME.withLocale(ROOT_LOCALE).withZone(ZONE_UTC),
|
||||||
|
DateTimeFormatter.ofPattern(RFC1036_PATTERN).withLocale(ROOT_LOCALE).withZone(ZONE_UTC),
|
||||||
|
DateTimeFormatter.ofPattern(ASCIITIME_PATTERN).withLocale(ROOT_LOCALE).withZone(ZONE_UTC)
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Instant parseDate(String date, int start, int end) {
|
||||||
|
int length = end - start;
|
||||||
|
if (length == 0) {
|
||||||
|
return null;
|
||||||
|
} else if (length < 0) {
|
||||||
|
throw new IllegalArgumentException("Can't have end < start");
|
||||||
|
} else if (length > 64) {
|
||||||
|
throw new IllegalArgumentException("Can't parse more than 64 chars," +
|
||||||
|
"looks like a user error or a malformed header");
|
||||||
|
}
|
||||||
|
return parseDate(date.substring(start, end));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Instant parseDate(String input) {
|
||||||
|
if (input == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int semicolonIndex = input.indexOf(';');
|
||||||
|
String trimmedDate = semicolonIndex >= 0 ? input.substring(0, semicolonIndex) : input;
|
||||||
|
for (DateTimeFormatter formatter : dateTimeFormatters) {
|
||||||
|
try {
|
||||||
|
return Instant.from(formatter.parse(trimmedDate));
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.xbib.netty.http.common.util;
|
||||||
|
|
||||||
|
public enum HMac {
|
||||||
|
HMAC_SHA1("HMacSHA1"),
|
||||||
|
HMAC_SHA256("HMacSHA256");
|
||||||
|
|
||||||
|
String algo;
|
||||||
|
|
||||||
|
HMac(String algo) {
|
||||||
|
this.algo = algo;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,16 +4,16 @@ import java.util.SortedSet;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
public class LimitedStringMap extends TreeMap<String, SortedSet<String>> {
|
public class LimitedMap<K, V> extends TreeMap<K, SortedSet<V>> {
|
||||||
|
|
||||||
private final int limit;
|
private final int limit;
|
||||||
|
|
||||||
public LimitedStringMap(int limit) {
|
public LimitedMap(int limit) {
|
||||||
this.limit = limit;
|
this.limit = limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SortedSet<String> put(String key, SortedSet<String> value) {
|
public SortedSet<V> put(K key, SortedSet<V> value) {
|
||||||
if (size() < limit) {
|
if (size() < limit) {
|
||||||
return super.put(key, value);
|
return super.put(key, value);
|
||||||
}
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.xbib.netty.http.common.util;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class LimitedSet<T extends CharSequence> extends TreeSet<T> {
|
||||||
|
|
||||||
|
private final int sizeLimit;
|
||||||
|
|
||||||
|
private final int elementMaximumLength;
|
||||||
|
|
||||||
|
public LimitedSet(int sizeLimit, int elementMaximumLength) {
|
||||||
|
this.sizeLimit = sizeLimit;
|
||||||
|
this.elementMaximumLength = elementMaximumLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean add(T t) {
|
||||||
|
Objects.requireNonNull(t);
|
||||||
|
if (size() < sizeLimit && t.length() <= elementMaximumLength) {
|
||||||
|
return super.add(t);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,29 +0,0 @@
|
||||||
package org.xbib.netty.http.common.util;
|
|
||||||
|
|
||||||
import java.util.SortedSet;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
public class LimitedSortedStringSet extends TreeSet<String> implements SortedSet<String> {
|
|
||||||
|
|
||||||
private final int sizeLimit;
|
|
||||||
|
|
||||||
private final int elementSizeLimit;
|
|
||||||
|
|
||||||
public LimitedSortedStringSet() {
|
|
||||||
this(1024, 65536);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LimitedSortedStringSet(int sizeLimit, int elementSizeLimit) {
|
|
||||||
this.sizeLimit = sizeLimit;
|
|
||||||
this.elementSizeLimit = elementSizeLimit;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean add(String string) {
|
|
||||||
if (size() < sizeLimit && string.length() <= elementSizeLimit ) {
|
|
||||||
return super.add(string);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
package org.xbib.netty.http.common.util;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.time.ZonedDateTime;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.time.format.DateTimeParseException;
|
|
||||||
|
|
||||||
public class TimeUtils {
|
|
||||||
|
|
||||||
public static String formatInstant(Instant instant) {
|
|
||||||
return DateTimeFormatter.RFC_1123_DATE_TIME
|
|
||||||
.format(ZonedDateTime.ofInstant(instant, ZoneOffset.UTC));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String formatMillis(long millis) {
|
|
||||||
return formatInstant(Instant.ofEpochMilli(millis));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String formatSeconds(long seconds) {
|
|
||||||
return formatInstant(Instant.now().plusSeconds(seconds));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String RFC1036_PATTERN = "EEE, dd-MMM-yyyy HH:mm:ss zzz";
|
|
||||||
|
|
||||||
private static final String ASCIITIME_PATTERN = "EEE MMM d HH:mm:ss yyyyy";
|
|
||||||
|
|
||||||
private static final DateTimeFormatter[] dateTimeFormatters = {
|
|
||||||
DateTimeFormatter.RFC_1123_DATE_TIME,
|
|
||||||
DateTimeFormatter.ofPattern(RFC1036_PATTERN),
|
|
||||||
DateTimeFormatter.ofPattern(ASCIITIME_PATTERN)
|
|
||||||
};
|
|
||||||
|
|
||||||
public static Instant parseDate(String date) {
|
|
||||||
if (date == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int semicolonIndex = date.indexOf(';');
|
|
||||||
String trimmedDate = semicolonIndex >= 0 ? date.substring(0, semicolonIndex) : date;
|
|
||||||
// RFC 2616 allows RFC 1123, RFC 1036, ASCII time
|
|
||||||
for (DateTimeFormatter formatter : dateTimeFormatters) {
|
|
||||||
try {
|
|
||||||
return Instant.from(formatter.withZone(ZoneId.of("UTC")).parse(trimmedDate));
|
|
||||||
} catch (DateTimeParseException e) {
|
|
||||||
// skip
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.xbib.netty.http.common.test;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.xbib.netty.http.common.util.CryptUtils;
|
||||||
|
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
class CryptUtilsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRfc2307() throws NoSuchAlgorithmException {
|
||||||
|
|
||||||
|
assertEquals("{md5}ixqZU8RhEpaoJ6v4xHgE1w==",
|
||||||
|
CryptUtils.md5("Hello"));
|
||||||
|
assertEquals("{sha}9/+ei3uy4Jtwk1pdeF4MxdnQq/A=",
|
||||||
|
CryptUtils.sha("Hello"));
|
||||||
|
assertEquals("{sha256}GF+NsyJx/iX1Yab8k4suJkMG7DBO2lGAB9F2SCY4GWk=",
|
||||||
|
CryptUtils.sha256("Hello"));
|
||||||
|
assertEquals("{sha512}NhX4DJ0pPtdAJof5SyLVjlKbjMeRb4+sf933+9WvTPd309eVp6AKFr9+fz+5Vh7puq5IDan+ehh2nnGIawPzFQ==",
|
||||||
|
CryptUtils.sha512("Hello"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testHmac() throws InvalidKeyException, NoSuchAlgorithmException {
|
||||||
|
assertEquals("Wgxn2SLeDKU+MGJQ5oWMH20sSUM=",
|
||||||
|
CryptUtils.hmacSHA1("hello", "world"));
|
||||||
|
assertEquals("PPp27xSTfBwOpRn4/AV6gPzQSnQg+Oi80KdWfCcuAHs=",
|
||||||
|
CryptUtils.hmacSHA256("hello", "world"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package org.xbib.netty.http.common;
|
package org.xbib.netty.http.common.test;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.xbib.netty.http.common.HttpParameters;
|
||||||
|
|
||||||
import java.nio.charset.MalformedInputException;
|
import java.nio.charset.MalformedInputException;
|
||||||
import java.nio.charset.UnmappableCharacterException;
|
import java.nio.charset.UnmappableCharacterException;
|
|
@ -0,0 +1,72 @@
|
||||||
|
package org.xbib.netty.http.common.test.cookie;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.xbib.netty.http.common.cookie.Cookie;
|
||||||
|
import org.xbib.netty.http.common.cookie.DefaultCookie;
|
||||||
|
import org.xbib.netty.http.common.cookie.Payload;
|
||||||
|
import org.xbib.netty.http.common.util.Codec;
|
||||||
|
import org.xbib.netty.http.common.util.HMac;
|
||||||
|
|
||||||
|
import java.nio.charset.MalformedInputException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.charset.UnmappableCharacterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class SignedCookieTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEncodeDefaultCookie() {
|
||||||
|
Base64Codec codec = new Base64Codec();
|
||||||
|
String cookieName = "SESS";
|
||||||
|
String domain = ".hbz-nrw.de";
|
||||||
|
String path = "/";
|
||||||
|
String id = "dummy";
|
||||||
|
Payload payload = new Payload(Codec.BASE64, HMac.HMAC_SHA256,
|
||||||
|
id, new String(codec.encode("Hello"), StandardCharsets.UTF_8),
|
||||||
|
"d9fd3a19707be6025b4f5a98c320745960a1b95144f040afcda2a4997dbae0cb");
|
||||||
|
DefaultCookie cookie = new DefaultCookie(cookieName, payload);
|
||||||
|
cookie.setDomain(domain);
|
||||||
|
cookie.setPath(path);
|
||||||
|
cookie.setMaxAge(3600);
|
||||||
|
cookie.setHttpOnly(true);
|
||||||
|
cookie.setSecure(true);
|
||||||
|
cookie.setSameSite(Cookie.SameSite.LAX);
|
||||||
|
assertEquals("dummy%3ASGVsbG8%3D%3AqEdbImuwfh7r%2BmOaShC3IjXhkdiiF3Y1RgSZ%2FFAZrQ4%3D", cookie.value());
|
||||||
|
assertEquals(domain, cookie.domain());
|
||||||
|
assertEquals(path, cookie.path());
|
||||||
|
assertEquals(3600, cookie.maxAge());
|
||||||
|
assertEquals(cookieName, cookie.name());
|
||||||
|
assertTrue(cookie.isHttpOnly());
|
||||||
|
assertTrue(cookie.isSecure());
|
||||||
|
assertEquals(Cookie.SameSite.LAX, cookie.sameSite());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCookieValue()
|
||||||
|
throws MalformedInputException, UnmappableCharacterException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
|
||||||
|
Base64Codec codec = new Base64Codec();
|
||||||
|
String rawCookieValue = "dummy%3ASGVsbG8%3D%3AqEdbImuwfh7r%2BmOaShC3IjXhkdiiF3Y1RgSZ%2FFAZrQ4%3D";
|
||||||
|
Payload payload = new Payload(Codec.BASE64, HMac.HMAC_SHA256,
|
||||||
|
rawCookieValue, "d9fd3a19707be6025b4f5a98c320745960a1b95144f040afcda2a4997dbae0cb");
|
||||||
|
DefaultCookie cookie = new DefaultCookie("SESS", payload);
|
||||||
|
assertEquals("dummy", payload.getPublicValue());
|
||||||
|
assertEquals("Hello", codec.decode(payload.getPrivateValue().getBytes(StandardCharsets.UTF_8)));
|
||||||
|
}
|
||||||
|
|
||||||
|
class Base64Codec {
|
||||||
|
|
||||||
|
byte[] encode(String payload) {
|
||||||
|
return Base64.getEncoder().encode(payload.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
String decode(byte[] bytes) {
|
||||||
|
return new String(Base64.getDecoder().decode(bytes), StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.netty.http.server.endpoint;
|
package org.xbib.netty.http.server;
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
import io.netty.handler.ssl.ApplicationProtocolConfig;
|
import io.netty.handler.ssl.ApplicationProtocolConfig;
|
||||||
|
@ -9,8 +9,8 @@ import io.netty.handler.ssl.SslContextBuilder;
|
||||||
import io.netty.handler.ssl.SslProvider;
|
import io.netty.handler.ssl.SslProvider;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.common.SecurityUtil;
|
import org.xbib.netty.http.common.SecurityUtil;
|
||||||
import org.xbib.netty.http.server.ServerRequest;
|
import org.xbib.netty.http.server.endpoint.HttpEndpoint;
|
||||||
import org.xbib.netty.http.server.ServerResponse;
|
import org.xbib.netty.http.server.endpoint.EndpointResolver;
|
||||||
import org.xbib.netty.http.server.endpoint.service.Service;
|
import org.xbib.netty.http.server.endpoint.service.Service;
|
||||||
import org.xbib.netty.http.server.security.tls.SelfSignedCertificate;
|
import org.xbib.netty.http.server.security.tls.SelfSignedCertificate;
|
||||||
|
|
||||||
|
@ -27,50 +27,46 @@ import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@code NamedServer} class represents a virtual server, with or without SSL.
|
* The {@code Domain} class represents a virtual server with a name, with or without SSL.
|
||||||
*/
|
*/
|
||||||
public class NamedServer {
|
public class Domain {
|
||||||
|
|
||||||
private final HttpAddress httpAddress;
|
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
private final SslContext sslContext;
|
|
||||||
|
|
||||||
private final Set<String> aliases;
|
private final Set<String> aliases;
|
||||||
|
|
||||||
private final List<EndpointResolver> endpointResolvers;
|
private final HttpAddress httpAddress;
|
||||||
|
|
||||||
protected NamedServer(HttpAddress httpAddress, String name, Set<String> aliases,
|
private final SslContext sslContext;
|
||||||
List<EndpointResolver> endpointResolvers) {
|
|
||||||
this(httpAddress, name, aliases, endpointResolvers, null);
|
private final List<EndpointResolver> endpointResolvers;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a {@code NamedServer} with the given name.
|
* Constructs a {@code NamedServer} with the given name.
|
||||||
*
|
*
|
||||||
* @param httpAddress HTTP address, used for determining if named server is secure or not
|
|
||||||
* @param name the name, or null if it is the default server
|
* @param name the name, or null if it is the default server
|
||||||
* @param aliases alias names for the named server
|
* @param aliases alias names for the named server
|
||||||
|
* @param httpAddress HTTP address, used for determining if named server is secure or not
|
||||||
* @param endpointResolvers the endpoint resolvers
|
* @param endpointResolvers the endpoint resolvers
|
||||||
* @param sslContext SSL context or null
|
* @param sslContext SSL context or null
|
||||||
*/
|
*/
|
||||||
protected NamedServer(HttpAddress httpAddress, String name, Set<String> aliases,
|
protected Domain(String name, Set<String> aliases,
|
||||||
|
HttpAddress httpAddress,
|
||||||
List<EndpointResolver> endpointResolvers,
|
List<EndpointResolver> endpointResolvers,
|
||||||
SslContext sslContext) {
|
SslContext sslContext) {
|
||||||
this.httpAddress = httpAddress;
|
this.httpAddress = httpAddress;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.sslContext = sslContext;
|
this.sslContext = sslContext;
|
||||||
this.aliases = aliases;
|
this.aliases = Collections.unmodifiableSet(aliases);
|
||||||
this.endpointResolvers = endpointResolvers;
|
this.endpointResolvers = endpointResolvers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder builder() {
|
public static Builder builder() {
|
||||||
return new Builder(HttpAddress.http1("localhost", 8008), "*");
|
return builder(HttpAddress.http1("localhost", 8008));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder builder(HttpAddress httpAddress) {
|
public static Builder builder(HttpAddress httpAddress) {
|
||||||
return new Builder(httpAddress, "*");
|
return builder(httpAddress, "*");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder builder(HttpAddress httpAddress, String serverName) {
|
public static Builder builder(HttpAddress httpAddress, String serverName) {
|
||||||
|
@ -100,7 +96,7 @@ public class NamedServer {
|
||||||
* @return the (unmodifiable) set of aliases (which may be empty)
|
* @return the (unmodifiable) set of aliases (which may be empty)
|
||||||
*/
|
*/
|
||||||
public Set<String> getAliases() {
|
public Set<String> getAliases() {
|
||||||
return Collections.unmodifiableSet(aliases);
|
return aliases;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void execute(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
public void execute(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
||||||
|
@ -113,6 +109,11 @@ public class NamedServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name + " (" + httpAddress + ") " + aliases;
|
||||||
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
private HttpAddress httpAddress;
|
private HttpAddress httpAddress;
|
||||||
|
@ -249,24 +250,24 @@ public class NamedServer {
|
||||||
|
|
||||||
public Builder singleEndpoint(String path, Service service) {
|
public Builder singleEndpoint(String path, Service service) {
|
||||||
addEndpointResolver(EndpointResolver.builder()
|
addEndpointResolver(EndpointResolver.builder()
|
||||||
.addEndpoint(Endpoint.builder().setPath(path).addFilter(service).build()).build());
|
.addEndpoint(HttpEndpoint.builder().setPath(path).addFilter(service).build()).build());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder singleEndpoint(String prefix, String path, Service service) {
|
public Builder singleEndpoint(String prefix, String path, Service service) {
|
||||||
addEndpointResolver(EndpointResolver.builder()
|
addEndpointResolver(EndpointResolver.builder()
|
||||||
.addEndpoint(Endpoint.builder().setPrefix(prefix).setPath(path).addFilter(service).build()).build());
|
.addEndpoint(HttpEndpoint.builder().setPrefix(prefix).setPath(path).addFilter(service).build()).build());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder singleEndpoint(String prefix, String path, Service service, String... methods) {
|
public Builder singleEndpoint(String prefix, String path, Service service, String... methods) {
|
||||||
addEndpointResolver(EndpointResolver.builder()
|
addEndpointResolver(EndpointResolver.builder()
|
||||||
.addEndpoint(Endpoint.builder().setPrefix(prefix).setPath(path).addFilter(service)
|
.addEndpoint(HttpEndpoint.builder().setPrefix(prefix).setPath(path).addFilter(service)
|
||||||
.setMethods(Arrays.asList(methods)).build()).build());
|
.setMethods(Arrays.asList(methods)).build()).build());
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NamedServer build() {
|
public Domain build() {
|
||||||
if (httpAddress.isSecure()) {
|
if (httpAddress.isSecure()) {
|
||||||
try {
|
try {
|
||||||
trustManagerFactory.init(trustManagerKeyStore);
|
trustManagerFactory.init(trustManagerKeyStore);
|
||||||
|
@ -281,12 +282,12 @@ public class NamedServer {
|
||||||
if (httpAddress.getVersion().majorVersion() == 2) {
|
if (httpAddress.getVersion().majorVersion() == 2) {
|
||||||
sslContextBuilder.applicationProtocolConfig(newApplicationProtocolConfig());
|
sslContextBuilder.applicationProtocolConfig(newApplicationProtocolConfig());
|
||||||
}
|
}
|
||||||
return new NamedServer(httpAddress, serverName, aliases, endpointResolvers, sslContextBuilder.build());
|
return new Domain(serverName, aliases, httpAddress, endpointResolvers, sslContextBuilder.build());
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
throw new RuntimeException(t);
|
throw new RuntimeException(t);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return new NamedServer(httpAddress, serverName, aliases, endpointResolvers);
|
return new Domain(serverName, aliases, httpAddress, endpointResolvers, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ import io.netty.util.DomainNameMapping;
|
||||||
import io.netty.util.DomainNameMappingBuilder;
|
import io.netty.util.DomainNameMappingBuilder;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.common.NetworkUtils;
|
import org.xbib.netty.http.common.NetworkUtils;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
|
||||||
import org.xbib.netty.http.server.handler.http.HttpChannelInitializer;
|
import org.xbib.netty.http.server.handler.http.HttpChannelInitializer;
|
||||||
import org.xbib.netty.http.server.handler.http2.Http2ChannelInitializer;
|
import org.xbib.netty.http.server.handler.http2.Http2ChannelInitializer;
|
||||||
import org.xbib.netty.http.common.SecurityUtil;
|
import org.xbib.netty.http.common.SecurityUtil;
|
||||||
|
@ -120,15 +119,15 @@ public final class Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder builder() {
|
public static Builder builder() {
|
||||||
return new Builder(HttpAddress.http1("localhost", 8008));
|
return builder(HttpAddress.http1("localhost", 8008));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder builder(HttpAddress httpAddress) {
|
public static Builder builder(HttpAddress httpAddress) {
|
||||||
return new Builder(httpAddress);
|
return new Builder(httpAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder builder(NamedServer namedServer) {
|
public static Builder builder(Domain domain) {
|
||||||
return new Builder(namedServer.getHttpAddress(), namedServer);
|
return new Builder(domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerConfig getServerConfig() {
|
public ServerConfig getServerConfig() {
|
||||||
|
@ -142,12 +141,12 @@ public final class Server {
|
||||||
* the default virtual host
|
* the default virtual host
|
||||||
* @return the virtual host with the given name, or null if it doesn't exist
|
* @return the virtual host with the given name, or null if it doesn't exist
|
||||||
*/
|
*/
|
||||||
public NamedServer getNamedServer(String name) {
|
public Domain getNamedServer(String name) {
|
||||||
return serverConfig.getNamedServers().get(name);
|
return serverConfig.getDomain(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public NamedServer getDefaultNamedServer() {
|
public Domain getDefaultNamedServer() {
|
||||||
return serverConfig.getDefaultNamedServer();
|
return serverConfig.getDefaultDomain();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -235,17 +234,17 @@ public final class Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
private DomainNameMapping<SslContext> createDomainNameMapping() {
|
private DomainNameMapping<SslContext> createDomainNameMapping() {
|
||||||
if (serverConfig.getDefaultNamedServer() == null) {
|
if (serverConfig.getDefaultDomain() == null) {
|
||||||
throw new IllegalStateException("no default named server (with name '*') configured, unable to continue");
|
throw new IllegalStateException("no default named server (with name '*') configured, unable to continue");
|
||||||
}
|
}
|
||||||
DomainNameMapping<SslContext> domainNameMapping = null;
|
DomainNameMapping<SslContext> domainNameMapping = null;
|
||||||
if (serverConfig.getAddress().isSecure() && serverConfig.getDefaultNamedServer().getSslContext() != null) {
|
if (serverConfig.getAddress().isSecure() && serverConfig.getDefaultDomain().getSslContext() != null) {
|
||||||
DomainNameMappingBuilder<SslContext> mappingBuilder =
|
DomainNameMappingBuilder<SslContext> mappingBuilder =
|
||||||
new DomainNameMappingBuilder<>(serverConfig.getDefaultNamedServer().getSslContext());
|
new DomainNameMappingBuilder<>(serverConfig.getDefaultDomain().getSslContext());
|
||||||
for (NamedServer namedServer : serverConfig.getNamedServers().values()) {
|
for (Domain domain : serverConfig.getDomains()) {
|
||||||
String name = namedServer.getName();
|
String name = domain.getName();
|
||||||
if (!"*".equals(name)) {
|
if (!"*".equals(name)) {
|
||||||
mappingBuilder.add(name, namedServer.getSslContext());
|
mappingBuilder.add(name, domain.getSslContext());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
domainNameMapping = mappingBuilder.build();
|
domainNameMapping = mappingBuilder.build();
|
||||||
|
@ -292,14 +291,14 @@ public final class Server {
|
||||||
|
|
||||||
private ServerConfig serverConfig;
|
private ServerConfig serverConfig;
|
||||||
|
|
||||||
Builder(HttpAddress httpAddress) {
|
private Builder(HttpAddress httpAddress) {
|
||||||
this(httpAddress, NamedServer.builder(httpAddress, "*").build());
|
this(Domain.builder(httpAddress, "*").build());
|
||||||
}
|
}
|
||||||
|
|
||||||
Builder(HttpAddress httpAddress, NamedServer defaultNamedServer) {
|
private Builder(Domain defaultDomain) {
|
||||||
this.serverConfig = new ServerConfig();
|
this.serverConfig = new ServerConfig();
|
||||||
this.serverConfig.setAddress(httpAddress);
|
this.serverConfig.setAddress(defaultDomain.getHttpAddress());
|
||||||
this.serverConfig.add(defaultNamedServer);
|
addServer(defaultDomain);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder enableDebug() {
|
public Builder enableDebug() {
|
||||||
|
@ -432,8 +431,9 @@ public final class Server {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder addServer(NamedServer namedServer) {
|
public Builder addServer(Domain domain) {
|
||||||
this.serverConfig.add(namedServer);
|
this.serverConfig.putDomain(domain);
|
||||||
|
logger.log(Level.FINE, "adding named server: " + domain);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@ import io.netty.channel.epoll.Epoll;
|
||||||
import io.netty.handler.codec.http2.Http2Settings;
|
import io.netty.handler.codec.http2.Http2Settings;
|
||||||
import io.netty.handler.logging.LogLevel;
|
import io.netty.handler.logging.LogLevel;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -188,10 +188,10 @@ public class ServerConfig {
|
||||||
|
|
||||||
private boolean installHttp2Upgrade = Defaults.INSTALL_HTTP_UPGRADE2;
|
private boolean installHttp2Upgrade = Defaults.INSTALL_HTTP_UPGRADE2;
|
||||||
|
|
||||||
private Map<String, NamedServer> namedServers;
|
private final Map<String, Domain> domains;
|
||||||
|
|
||||||
public ServerConfig() {
|
public ServerConfig() {
|
||||||
this.namedServers = new LinkedHashMap<>();
|
this.domains = new LinkedHashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerConfig enableDebug() {
|
public ServerConfig enableDebug() {
|
||||||
|
@ -425,20 +425,41 @@ public class ServerConfig {
|
||||||
return http2Settings;
|
return http2Settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerConfig add(NamedServer namedServer) {
|
public ServerConfig putDomain(Domain domain) {
|
||||||
this.namedServers.put(namedServer.getName(), namedServer);
|
synchronized (domains) {
|
||||||
for (String alias : namedServer.getAliases()) {
|
domains.put(domain.getName(), domain);
|
||||||
this.namedServers.put(alias, namedServer);
|
for (String alias : domain.getAliases()) {
|
||||||
|
domains.put(alias, domain);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NamedServer getDefaultNamedServer() {
|
public Collection<Domain> getDomains() {
|
||||||
return namedServers.get("*");
|
return domains.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, NamedServer> getNamedServers() {
|
public ServerConfig removeDomain(String name) {
|
||||||
return namedServers;
|
domains.remove(name);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerConfig removeDomain(Domain domain) {
|
||||||
|
synchronized (domains) {
|
||||||
|
domains.remove(domain.getName());
|
||||||
|
for (String alias : domain.getAliases()) {
|
||||||
|
domains.remove(alias, domain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Domain getDefaultDomain() {
|
||||||
|
return getDomain("*");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Domain getDomain(String name) {
|
||||||
|
return domains.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,24 +1,19 @@
|
||||||
package org.xbib.netty.http.server;
|
package org.xbib.netty.http.server;
|
||||||
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
import io.netty.handler.codec.http.HttpHeaders;
|
||||||
import io.netty.handler.ssl.SslContext;
|
import io.netty.handler.codec.http.HttpMethod;
|
||||||
import org.xbib.net.URL;
|
import org.xbib.net.URL;
|
||||||
import org.xbib.netty.http.common.HttpParameters;
|
import org.xbib.netty.http.common.HttpParameters;
|
||||||
|
import org.xbib.netty.http.server.endpoint.EndpointInfo;
|
||||||
|
|
||||||
import javax.net.ssl.SSLSession;
|
import javax.net.ssl.SSLSession;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
|
|
||||||
|
|
||||||
public interface ServerRequest {
|
public interface ServerRequest {
|
||||||
|
|
||||||
ChannelHandlerContext getChannelHandlerContext();
|
|
||||||
|
|
||||||
FullHttpRequest getRequest();
|
|
||||||
|
|
||||||
URL getURL();
|
URL getURL();
|
||||||
|
|
||||||
EndpointInfo getEndpointInfo();
|
EndpointInfo getEndpointInfo();
|
||||||
|
@ -29,9 +24,13 @@ public interface ServerRequest {
|
||||||
|
|
||||||
void addPathParameter(String key, String value) throws IOException;
|
void addPathParameter(String key, String value) throws IOException;
|
||||||
|
|
||||||
|
void createParameters() throws IOException;
|
||||||
|
|
||||||
Map<String, String> getPathParameters();
|
Map<String, String> getPathParameters();
|
||||||
|
|
||||||
void createParameters() throws IOException;
|
HttpMethod getMethod();
|
||||||
|
|
||||||
|
HttpHeaders getHeaders();
|
||||||
|
|
||||||
HttpParameters getParameters();
|
HttpParameters getParameters();
|
||||||
|
|
||||||
|
@ -47,59 +46,6 @@ public interface ServerRequest {
|
||||||
|
|
||||||
SSLSession getSession();
|
SSLSession getSession();
|
||||||
|
|
||||||
class EndpointInfo implements Comparable<EndpointInfo> {
|
ByteBuf getContent();
|
||||||
|
|
||||||
private final String path;
|
|
||||||
|
|
||||||
private final String method;
|
|
||||||
|
|
||||||
private final String contentType;
|
|
||||||
|
|
||||||
public EndpointInfo(ServerRequest serverRequest) {
|
|
||||||
this.path = extractPath(serverRequest.getRequest().uri());
|
|
||||||
this.method = serverRequest.getRequest().method().name();
|
|
||||||
this.contentType = serverRequest.getRequest().headers().get(CONTENT_TYPE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPath() {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMethod() {
|
|
||||||
return method;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getContentType() {
|
|
||||||
return contentType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "[EndpointInfo:path=" + path + ",method=" + method + ",contentType=" + contentType + "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return toString().hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
return o instanceof EndpointInfo && toString().equals(o.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(EndpointInfo o) {
|
|
||||||
return toString().compareTo(o.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String extractPath(String uri) {
|
|
||||||
String path = uri;
|
|
||||||
int pos = uri.lastIndexOf('#');
|
|
||||||
path = pos >= 0 ? path.substring(0, pos) : path;
|
|
||||||
pos = uri.lastIndexOf('?');
|
|
||||||
path = pos >= 0 ? path.substring(0, pos) : path;
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,10 @@ public interface ServerResponse {
|
||||||
|
|
||||||
void write(ChunkedInput<ByteBuf> chunkedInput);
|
void write(ChunkedInput<ByteBuf> chunkedInput);
|
||||||
|
|
||||||
|
static void write(ServerResponse serverResponse, int status) {
|
||||||
|
write(serverResponse, HttpResponseStatus.valueOf(status));
|
||||||
|
}
|
||||||
|
|
||||||
static void write(ServerResponse serverResponse, HttpResponseStatus status) {
|
static void write(ServerResponse serverResponse, HttpResponseStatus status) {
|
||||||
write(serverResponse, status, "application/octet-stream", status.reasonPhrase());
|
write(serverResponse, status, "application/octet-stream", status.reasonPhrase());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.netty.http.server.endpoint;
|
package org.xbib.netty.http.server.annotation;
|
||||||
|
|
||||||
import org.xbib.netty.http.server.endpoint.service.Service;
|
import org.xbib.netty.http.server.endpoint.service.Service;
|
||||||
|
|
||||||
|
@ -8,26 +8,28 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@code Context} annotation decorates methods which are mapped
|
* The {@code Endpoint} annotation decorates methods which are mapped
|
||||||
* to a context path within the server, and provide its contents.
|
* to a HTTP endpoint within the server, and provide its contents.
|
||||||
* The annotated methods must have the same signature and contract
|
* The annotated methods must have the same signature and contract
|
||||||
* as {@link Service#handle}, but can have arbitrary names.
|
* as {@link Service#handle}, but can have arbitrary names.
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.METHOD)
|
@Target(ElementType.METHOD)
|
||||||
public @interface Context {
|
public @interface Endpoint {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The context (path) that this field maps to (must begin with '/').
|
* The path that this field maps to (must begin with '/').
|
||||||
*
|
*
|
||||||
* @return the context (path) that this field maps to
|
* @return the path that this field maps to
|
||||||
*/
|
*/
|
||||||
String value();
|
String path();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The HTTP methods supported by this context handler (default is "GET" and "HEAD").
|
* The HTTP methods supported by this endpoint (default is "GET" and "HEAD").
|
||||||
*
|
*
|
||||||
* @return the HTTP methods supported by this context handler
|
* @return the HTTP methods supported by this endpoint
|
||||||
*/
|
*/
|
||||||
String[] methods() default {"GET", "HEAD"};
|
String[] methods() default {"GET", "HEAD"};
|
||||||
|
|
||||||
|
String[] contentTypes();
|
||||||
}
|
}
|
|
@ -6,9 +6,9 @@ import org.xbib.netty.http.common.cookie.CookieHeaderNames;
|
||||||
import org.xbib.netty.http.common.cookie.DefaultCookie;
|
import org.xbib.netty.http.common.cookie.DefaultCookie;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A <a href="http://tools.ietf.org/html/rfc6265">RFC6265</a> compliant cookie decoder to be used server side.
|
* A <a href="http://tools.ietf.org/html/rfc6265">RFC6265</a> compliant cookie decoder to be used server side.
|
||||||
|
@ -20,13 +20,13 @@ import java.util.TreeSet;
|
||||||
*/
|
*/
|
||||||
public final class ServerCookieDecoder extends CookieDecoder {
|
public final class ServerCookieDecoder extends CookieDecoder {
|
||||||
|
|
||||||
private static final String RFC2965_VERSION = "\\$Version";
|
private static final String RFC2965_VERSION = "$Version";
|
||||||
|
|
||||||
private static final String RFC2965_PATH = "\\$" + CookieHeaderNames.PATH;
|
private static final String RFC2965_PATH = "$" + CookieHeaderNames.PATH;
|
||||||
|
|
||||||
private static final String RFC2965_DOMAIN = "\\$" + CookieHeaderNames.DOMAIN;
|
private static final String RFC2965_DOMAIN = "$" + CookieHeaderNames.DOMAIN;
|
||||||
|
|
||||||
private static final String RFC2965_PORT = "\\$Port";
|
private static final String RFC2965_PORT = "$Port";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strict encoder that validates that name and value chars are in the valid scope
|
* Strict encoder that validates that name and value chars are in the valid scope
|
||||||
|
@ -54,7 +54,7 @@ public final class ServerCookieDecoder extends CookieDecoder {
|
||||||
if (headerLen == 0) {
|
if (headerLen == 0) {
|
||||||
return Collections.emptySet();
|
return Collections.emptySet();
|
||||||
}
|
}
|
||||||
Set<Cookie> cookies = new TreeSet<Cookie>();
|
Set<Cookie> cookies = new LinkedHashSet<>();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
boolean rfc2965Style = false;
|
boolean rfc2965Style = false;
|
||||||
if (header.regionMatches(true, 0, RFC2965_VERSION, 0, RFC2965_VERSION.length())) {
|
if (header.regionMatches(true, 0, RFC2965_VERSION, 0, RFC2965_VERSION.length())) {
|
||||||
|
|
|
@ -5,13 +5,16 @@ import org.xbib.netty.http.common.cookie.CookieEncoder;
|
||||||
import org.xbib.netty.http.common.cookie.CookieHeaderNames;
|
import org.xbib.netty.http.common.cookie.CookieHeaderNames;
|
||||||
import org.xbib.netty.http.common.cookie.CookieUtil;
|
import org.xbib.netty.http.common.cookie.CookieUtil;
|
||||||
import org.xbib.netty.http.common.cookie.DefaultCookie;
|
import org.xbib.netty.http.common.cookie.DefaultCookie;
|
||||||
|
import org.xbib.netty.http.common.util.DateTimeUtils;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@ -80,10 +83,10 @@ public final class ServerCookieEncoder extends CookieEncoder {
|
||||||
}
|
}
|
||||||
if (cookie.maxAge() != Long.MIN_VALUE) {
|
if (cookie.maxAge() != Long.MIN_VALUE) {
|
||||||
CookieUtil.add(buf, CookieHeaderNames.MAX_AGE, cookie.maxAge());
|
CookieUtil.add(buf, CookieHeaderNames.MAX_AGE, cookie.maxAge());
|
||||||
//Date expires = new Date(cookie.maxAge() * 1000 + System.currentTimeMillis())
|
Instant expires = Instant.ofEpochMilli(cookie.maxAge() * 1000 + System.currentTimeMillis());
|
||||||
buf.append(CookieHeaderNames.EXPIRES);
|
buf.append(CookieHeaderNames.EXPIRES);
|
||||||
buf.append(CookieUtil.EQUALS);
|
buf.append(CookieUtil.EQUALS);
|
||||||
//DateFormatter.append(expires, buf)
|
buf.append(DateTimeUtils.formatMillis(expires.toEpochMilli()));
|
||||||
buf.append(CookieUtil.SEMICOLON);
|
buf.append(CookieUtil.SEMICOLON);
|
||||||
buf.append(CookieUtil.SP);
|
buf.append(CookieUtil.SP);
|
||||||
}
|
}
|
||||||
|
@ -100,7 +103,9 @@ public final class ServerCookieEncoder extends CookieEncoder {
|
||||||
CookieUtil.add(buf, CookieHeaderNames.HTTPONLY);
|
CookieUtil.add(buf, CookieHeaderNames.HTTPONLY);
|
||||||
}
|
}
|
||||||
if (cookie.sameSite() != null) {
|
if (cookie.sameSite() != null) {
|
||||||
CookieUtil.add(buf, CookieHeaderNames.SAMESITE, cookie.sameSite());
|
String s = cookie.sameSite().name();
|
||||||
|
CookieUtil.add(buf, CookieHeaderNames.SAMESITE,
|
||||||
|
s.substring(0, 1).toUpperCase(Locale.ROOT) + s.substring(1).toLowerCase(Locale.ROOT));
|
||||||
}
|
}
|
||||||
return CookieUtil.stripTrailingSeparator(buf);
|
return CookieUtil.stripTrailingSeparator(buf);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,174 +1,16 @@
|
||||||
package org.xbib.netty.http.server.endpoint;
|
package org.xbib.netty.http.server.endpoint;
|
||||||
|
|
||||||
import org.xbib.net.QueryParameters;
|
|
||||||
import org.xbib.net.path.PathMatcher;
|
|
||||||
import org.xbib.net.path.PathNormalizer;
|
|
||||||
import org.xbib.netty.http.server.ServerRequest;
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
import org.xbib.netty.http.server.ServerResponse;
|
|
||||||
import org.xbib.netty.http.server.endpoint.service.Service;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class Endpoint {
|
public interface Endpoint {
|
||||||
|
|
||||||
private static final PathMatcher pathMatcher = new PathMatcher();
|
String getPrefix();
|
||||||
|
|
||||||
public static final List<String> DEFAULT_METHODS = Arrays.asList("GET", "HEAD");
|
String getPath();
|
||||||
|
|
||||||
private final String prefix;
|
boolean matches(EndpointInfo info);
|
||||||
|
|
||||||
private final String path;
|
void resolveUriTemplate(ServerRequest serverRequest) throws IOException;
|
||||||
|
|
||||||
private final List<String> methods;
|
|
||||||
|
|
||||||
private final List<String> contentTypes;
|
|
||||||
|
|
||||||
private final List<Service> filters;
|
|
||||||
|
|
||||||
private Endpoint(String prefix, String path,
|
|
||||||
List<String> methods, List<String> contentTypes, List<Service> filters) {
|
|
||||||
this.prefix = PathNormalizer.normalize(prefix);
|
|
||||||
this.path = PathNormalizer.normalize(path);
|
|
||||||
this.methods = methods;
|
|
||||||
this.contentTypes = contentTypes;
|
|
||||||
this.filters = filters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder builder() {
|
|
||||||
return new Builder();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder builder(Endpoint endpoint) {
|
|
||||||
return new Builder()
|
|
||||||
.setPrefix(endpoint.prefix)
|
|
||||||
.setPath(endpoint.path)
|
|
||||||
.setMethods(endpoint.methods)
|
|
||||||
.setContentTypes(endpoint.contentTypes)
|
|
||||||
.setFilters(endpoint.filters);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPrefix() {
|
|
||||||
return prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPath() {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean matches(ServerRequest.EndpointInfo info) {
|
|
||||||
return pathMatcher.match(prefix + path, info.getPath()) &&
|
|
||||||
(methods == null || methods.isEmpty() || (methods.contains(info.getMethod()))) &&
|
|
||||||
(contentTypes == null || contentTypes.isEmpty() || info.getContentType() == null ||
|
|
||||||
contentTypes.stream().anyMatch(info.getContentType()::startsWith));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resolveUriTemplate(ServerRequest serverRequest) throws IOException {
|
|
||||||
if (pathMatcher.match(prefix + path, serverRequest.getRequest().uri())) {
|
|
||||||
QueryParameters queryParameters = pathMatcher.extractUriTemplateVariables(prefix + path, serverRequest.getRequest().uri());
|
|
||||||
for (QueryParameters.Pair<String, String> pair : queryParameters) {
|
|
||||||
serverRequest.addPathParameter(pair.getFirst(), pair.getSecond());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void executeFilters(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
|
||||||
serverRequest.setContext(pathMatcher.tokenizePath(getPrefix()));
|
|
||||||
for (Service service : filters) {
|
|
||||||
service.handle(serverRequest, serverResponse);
|
|
||||||
if (serverResponse.getStatus() != null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Endpoint[prefix=" + prefix + ",path=" + path + ",methods=" + methods + ",contentTypes=" + contentTypes + " --> " + filters +"]";
|
|
||||||
}
|
|
||||||
|
|
||||||
static class EndpointPathComparator implements Comparator<Endpoint> {
|
|
||||||
|
|
||||||
private final Comparator<String> pathComparator;
|
|
||||||
|
|
||||||
EndpointPathComparator(String path) {
|
|
||||||
this.pathComparator = pathMatcher.getPatternComparator(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(Endpoint endpoint1, Endpoint endpoint2) {
|
|
||||||
return pathComparator.compare(endpoint1.path, endpoint2.path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
|
|
||||||
private String prefix;
|
|
||||||
|
|
||||||
private String path;
|
|
||||||
|
|
||||||
private List<String> methods;
|
|
||||||
|
|
||||||
private List<String> contentTypes;
|
|
||||||
|
|
||||||
private List<Service> filters;
|
|
||||||
|
|
||||||
Builder() {
|
|
||||||
this.prefix = "/";
|
|
||||||
this.path = "/**";
|
|
||||||
this.methods = new ArrayList<>();
|
|
||||||
this.contentTypes = new ArrayList<>();
|
|
||||||
this.filters = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setPrefix(String prefix) {
|
|
||||||
this.prefix = prefix;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setPath(String path) {
|
|
||||||
this.path = path;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setMethods(List<String> methods) {
|
|
||||||
this.methods = methods;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addMethod(String method) {
|
|
||||||
methods.add(method);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setContentTypes(List<String> contentTypes) {
|
|
||||||
this.contentTypes = contentTypes;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addContentType(String contentType) {
|
|
||||||
this.contentTypes.add(contentType);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder setFilters(List<Service> filters) {
|
|
||||||
this.filters = filters;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder addFilter(Service filter) {
|
|
||||||
this.filters.add(filter);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Endpoint build() {
|
|
||||||
if (methods.isEmpty()) {
|
|
||||||
methods = DEFAULT_METHODS;
|
|
||||||
}
|
|
||||||
return new Endpoint(prefix, path, methods, contentTypes, filters);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,5 +8,5 @@ import java.io.IOException;
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface EndpointDispatcher {
|
public interface EndpointDispatcher {
|
||||||
|
|
||||||
void dispatch(Endpoint endpoint, ServerRequest serverRequest, ServerResponse serverResponse) throws IOException;
|
void dispatch(HttpEndpoint endpoint, ServerRequest serverRequest, ServerResponse serverResponse) throws IOException;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
package org.xbib.netty.http.server.endpoint;
|
||||||
|
|
||||||
|
import org.xbib.netty.http.server.transport.HttpServerRequest;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
|
||||||
|
|
||||||
|
public class EndpointInfo implements Comparable<EndpointInfo> {
|
||||||
|
|
||||||
|
private final String path;
|
||||||
|
|
||||||
|
private final String method;
|
||||||
|
|
||||||
|
private final String contentType;
|
||||||
|
|
||||||
|
public EndpointInfo(HttpServerRequest serverRequest) {
|
||||||
|
this.path = extractPath(serverRequest.getRequest().uri());
|
||||||
|
this.method = serverRequest.getRequest().method().name();
|
||||||
|
this.contentType = serverRequest.getRequest().headers().get(CONTENT_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMethod() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContentType() {
|
||||||
|
return contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[EndpointInfo:path=" + path + ",method=" + method + ",contentType=" + contentType + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return toString().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return o instanceof EndpointInfo && toString().equals(o.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(EndpointInfo o) {
|
||||||
|
return toString().compareTo(o.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractPath(String uri) {
|
||||||
|
String path = uri;
|
||||||
|
int pos = uri.lastIndexOf('#');
|
||||||
|
path = pos >= 0 ? path.substring(0, pos) : path;
|
||||||
|
pos = uri.lastIndexOf('?');
|
||||||
|
path = pos >= 0 ? path.substring(0, pos) : path;
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package org.xbib.netty.http.server.endpoint;
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
import org.xbib.netty.http.server.ServerRequest;
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
import org.xbib.netty.http.server.ServerResponse;
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
|
import org.xbib.netty.http.server.annotation.Endpoint;
|
||||||
import org.xbib.netty.http.server.endpoint.service.MethodService;
|
import org.xbib.netty.http.server.endpoint.service.MethodService;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -20,32 +21,32 @@ public class EndpointResolver {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(EndpointResolver.class.getName());
|
private static final Logger logger = Logger.getLogger(EndpointResolver.class.getName());
|
||||||
|
|
||||||
private final Endpoint defaultEndpoint;
|
private final HttpEndpoint defaultEndpoint;
|
||||||
|
|
||||||
private final List<Endpoint> endpoints;
|
private final List<HttpEndpoint> endpoints;
|
||||||
|
|
||||||
private final EndpointDispatcher endpointDispatcher;
|
private final EndpointDispatcher endpointDispatcher;
|
||||||
|
|
||||||
private final LRUCache<ServerRequest.EndpointInfo, List<Endpoint>> cache;
|
private final LRUCache<EndpointInfo, List<HttpEndpoint>> endpointInfos;
|
||||||
|
|
||||||
private EndpointResolver(Endpoint defaultEndpoint,
|
private EndpointResolver(HttpEndpoint defaultEndpoint,
|
||||||
List<Endpoint> endpoints,
|
List<HttpEndpoint> endpoints,
|
||||||
EndpointDispatcher endpointDispatcher,
|
EndpointDispatcher endpointDispatcher,
|
||||||
int cacheSize) {
|
int cacheSize) {
|
||||||
this.defaultEndpoint = defaultEndpoint == null ? createDefaultEndpoint() : defaultEndpoint;
|
this.defaultEndpoint = defaultEndpoint == null ? createDefaultEndpoint() : defaultEndpoint;
|
||||||
this.endpoints = endpoints;
|
this.endpoints = endpoints;
|
||||||
this.endpointDispatcher = endpointDispatcher;
|
this.endpointDispatcher = endpointDispatcher;
|
||||||
this.cache = new LRUCache<>(cacheSize);
|
this.endpointInfos = new LRUCache<>(cacheSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resolve(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
public void resolve(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
||||||
ServerRequest.EndpointInfo endpointInfo = serverRequest.getEndpointInfo();
|
EndpointInfo endpointInfo = serverRequest.getEndpointInfo();
|
||||||
cache.putIfAbsent(endpointInfo, endpoints.stream()
|
endpointInfos.putIfAbsent(endpointInfo, endpoints.stream()
|
||||||
.filter(endpoint -> endpoint.matches(endpointInfo))
|
.filter(endpoint -> endpoint.matches(endpointInfo))
|
||||||
.sorted(new Endpoint.EndpointPathComparator(endpointInfo.getPath())).collect(Collectors.toList()));
|
.sorted(new HttpEndpoint.EndpointPathComparator(endpointInfo.getPath())).collect(Collectors.toList()));
|
||||||
List<Endpoint> matchingEndpoints = cache.get(endpointInfo);
|
List<HttpEndpoint> matchingEndpoints = endpointInfos.get(endpointInfo);
|
||||||
if (logger.isLoggable(Level.FINEST)) {
|
if (logger.isLoggable(Level.FINE)) {
|
||||||
logger.log(Level.FINEST, "endpoint info = " + endpointInfo + " matching endpoints = " + matchingEndpoints + " cache size=" + cache.size());
|
logger.log(Level.FINE, "endpoint info = " + endpointInfo + " matching endpoints = " + matchingEndpoints + " cache size=" + endpointInfos.size());
|
||||||
}
|
}
|
||||||
if (matchingEndpoints.isEmpty()) {
|
if (matchingEndpoints.isEmpty()) {
|
||||||
if (defaultEndpoint != null) {
|
if (defaultEndpoint != null) {
|
||||||
|
@ -58,7 +59,7 @@ public class EndpointResolver {
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_IMPLEMENTED);
|
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_IMPLEMENTED);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (Endpoint endpoint : matchingEndpoints) {
|
for (HttpEndpoint endpoint : matchingEndpoints) {
|
||||||
endpoint.resolveUriTemplate(serverRequest);
|
endpoint.resolveUriTemplate(serverRequest);
|
||||||
endpoint.executeFilters(serverRequest, serverResponse);
|
endpoint.executeFilters(serverRequest, serverResponse);
|
||||||
if (serverResponse.getStatus() != null) {
|
if (serverResponse.getStatus() != null) {
|
||||||
|
@ -66,7 +67,7 @@ public class EndpointResolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (endpointDispatcher != null) {
|
if (endpointDispatcher != null) {
|
||||||
for (Endpoint endpoint : matchingEndpoints) {
|
for (HttpEndpoint endpoint : matchingEndpoints) {
|
||||||
endpointDispatcher.dispatch(endpoint, serverRequest, serverResponse);
|
endpointDispatcher.dispatch(endpoint, serverRequest, serverResponse);
|
||||||
if (serverResponse.getStatus() != null) {
|
if (serverResponse.getStatus() != null) {
|
||||||
break;
|
break;
|
||||||
|
@ -76,12 +77,12 @@ public class EndpointResolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LRUCache<ServerRequest.EndpointInfo, List<Endpoint>> getCache() {
|
public Map<EndpointInfo, List<HttpEndpoint>> getEndpointInfos() {
|
||||||
return cache;
|
return endpointInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Endpoint createDefaultEndpoint() {
|
protected HttpEndpoint createDefaultEndpoint() {
|
||||||
return Endpoint.builder()
|
return HttpEndpoint.builder()
|
||||||
.setPath("/**")
|
.setPath("/**")
|
||||||
.addMethod("GET")
|
.addMethod("GET")
|
||||||
.addMethod("HEAD")
|
.addMethod("HEAD")
|
||||||
|
@ -122,9 +123,9 @@ public class EndpointResolver {
|
||||||
|
|
||||||
private String prefix;
|
private String prefix;
|
||||||
|
|
||||||
private Endpoint defaultEndpoint;
|
private HttpEndpoint defaultEndpoint;
|
||||||
|
|
||||||
private List<Endpoint> endpoints;
|
private List<HttpEndpoint> endpoints;
|
||||||
|
|
||||||
private EndpointDispatcher endpointDispatcher;
|
private EndpointDispatcher endpointDispatcher;
|
||||||
|
|
||||||
|
@ -143,7 +144,7 @@ public class EndpointResolver {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setDefaultEndpoint(Endpoint endpoint) {
|
public Builder setDefaultEndpoint(HttpEndpoint endpoint) {
|
||||||
this.defaultEndpoint = endpoint;
|
this.defaultEndpoint = endpoint;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -154,13 +155,13 @@ public class EndpointResolver {
|
||||||
* @param endpoint the endpoint
|
* @param endpoint the endpoint
|
||||||
* @return this builder
|
* @return this builder
|
||||||
*/
|
*/
|
||||||
public Builder addEndpoint(Endpoint endpoint) {
|
public Builder addEndpoint(HttpEndpoint endpoint) {
|
||||||
if (endpoint.getPrefix().equals("/") && prefix != null && !prefix.isEmpty()) {
|
if (endpoint.getPrefix().equals("/") && prefix != null && !prefix.isEmpty()) {
|
||||||
Endpoint thisEndpoint = Endpoint.builder(endpoint).setPrefix(prefix).build();
|
HttpEndpoint thisEndpoint = HttpEndpoint.builder(endpoint).setPrefix(prefix).build();
|
||||||
logger.log(Level.FINEST, "adding endpoint = " + thisEndpoint);
|
logger.log(Level.FINE, "adding endpoint = " + thisEndpoint);
|
||||||
endpoints.add(thisEndpoint);
|
endpoints.add(thisEndpoint);
|
||||||
} else {
|
} else {
|
||||||
logger.log(Level.FINEST, "adding endpoint = " + endpoint);
|
logger.log(Level.FINE, "adding endpoint = " + endpoint);
|
||||||
endpoints.add(endpoint);
|
endpoints.add(endpoint);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
|
@ -168,20 +169,22 @@ public class EndpointResolver {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a service for the methods of the given object that
|
* Adds a service for the methods of the given object that
|
||||||
* are annotated with the {@link Context} annotation.
|
* are annotated with the {@link Endpoint} annotation.
|
||||||
* @param classWithAnnotatedMethods class with annotated methods
|
* @param classWithAnnotatedMethods class with annotated methods
|
||||||
* @return this builder
|
* @return this builder
|
||||||
*/
|
*/
|
||||||
public Builder addEndpoint(Object classWithAnnotatedMethods) {
|
public Builder addEndpoint(Object classWithAnnotatedMethods) {
|
||||||
for (Class<?> clazz = classWithAnnotatedMethods.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
|
for (Class<?> clazz = classWithAnnotatedMethods.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
|
||||||
for (Method method : clazz.getDeclaredMethods()) {
|
for (Method method : clazz.getDeclaredMethods()) {
|
||||||
Context context = method.getAnnotation(Context.class);
|
Endpoint endpoint = method.getAnnotation(Endpoint.class);
|
||||||
if (context != null) {
|
if (endpoint != null) {
|
||||||
addEndpoint(Endpoint.builder()
|
MethodService methodService = new MethodService(method, classWithAnnotatedMethods);
|
||||||
|
addEndpoint(HttpEndpoint.builder()
|
||||||
.setPrefix(prefix)
|
.setPrefix(prefix)
|
||||||
.setPath(context.value())
|
.setPath(endpoint.path())
|
||||||
.setMethods(Arrays.asList(context.methods()))
|
.setMethods(Arrays.asList(endpoint.methods()))
|
||||||
.addFilter(new MethodService(method, classWithAnnotatedMethods))
|
.setContentTypes(Arrays.asList(endpoint.contentTypes()))
|
||||||
|
.addFilter(methodService)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
package org.xbib.netty.http.server.endpoint;
|
||||||
|
|
||||||
|
import org.xbib.net.QueryParameters;
|
||||||
|
import org.xbib.net.path.PathMatcher;
|
||||||
|
import org.xbib.net.path.PathNormalizer;
|
||||||
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
|
import org.xbib.netty.http.server.endpoint.service.Service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class HttpEndpoint {
|
||||||
|
|
||||||
|
private static final PathMatcher pathMatcher = new PathMatcher();
|
||||||
|
|
||||||
|
private static final List<String> DEFAULT_METHODS = Arrays.asList("GET", "HEAD");
|
||||||
|
|
||||||
|
private final String prefix;
|
||||||
|
|
||||||
|
private final String path;
|
||||||
|
|
||||||
|
private final List<String> methods;
|
||||||
|
|
||||||
|
private final List<String> contentTypes;
|
||||||
|
|
||||||
|
private final List<Service> filters;
|
||||||
|
|
||||||
|
private HttpEndpoint(String prefix, String path, List<String> methods, List<String> contentTypes, List<Service> filters) {
|
||||||
|
this.prefix = PathNormalizer.normalize(prefix);
|
||||||
|
this.path = PathNormalizer.normalize(path);
|
||||||
|
this.methods = methods;
|
||||||
|
this.contentTypes = contentTypes;
|
||||||
|
this.filters = filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder(HttpEndpoint endpoint) {
|
||||||
|
return new Builder()
|
||||||
|
.setPrefix(endpoint.prefix)
|
||||||
|
.setPath(endpoint.path)
|
||||||
|
.setMethods(endpoint.methods)
|
||||||
|
.setContentTypes(endpoint.contentTypes)
|
||||||
|
.setFilters(endpoint.filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPrefix() {
|
||||||
|
return prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matches(EndpointInfo info) {
|
||||||
|
return pathMatcher.match(prefix + path, info.getPath()) &&
|
||||||
|
(methods == null || methods.isEmpty() || (methods.contains(info.getMethod()))) &&
|
||||||
|
(contentTypes == null || contentTypes.isEmpty() || info.getContentType() == null ||
|
||||||
|
contentTypes.stream().anyMatch(info.getContentType()::startsWith));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resolveUriTemplate(ServerRequest serverRequest) throws IOException {
|
||||||
|
if (pathMatcher.match(prefix + path, serverRequest.getEffectiveRequestPath() /*serverRequest.getRequest().uri()*/ )) {
|
||||||
|
QueryParameters queryParameters = pathMatcher.extractUriTemplateVariables(prefix + path,
|
||||||
|
serverRequest.getEffectiveRequestPath() /*serverRequest.getRequest().uri()*/ );
|
||||||
|
for (QueryParameters.Pair<String, String> pair : queryParameters) {
|
||||||
|
serverRequest.addPathParameter(pair.getFirst(), pair.getSecond());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void executeFilters(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
||||||
|
serverRequest.setContext(pathMatcher.tokenizePath(getPrefix()));
|
||||||
|
for (Service service : filters) {
|
||||||
|
service.handle(serverRequest, serverResponse);
|
||||||
|
if (serverResponse.getStatus() != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Endpoint[prefix=" + prefix + ",path=" + path + ",methods=" + methods + ",contentTypes=" + contentTypes + " --> " + filters +"]";
|
||||||
|
}
|
||||||
|
|
||||||
|
static class EndpointPathComparator implements Comparator<HttpEndpoint> {
|
||||||
|
|
||||||
|
private final Comparator<String> pathComparator;
|
||||||
|
|
||||||
|
EndpointPathComparator(String path) {
|
||||||
|
this.pathComparator = pathMatcher.getPatternComparator(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(HttpEndpoint endpoint1, HttpEndpoint endpoint2) {
|
||||||
|
return pathComparator.compare(endpoint1.path, endpoint2.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
private String prefix;
|
||||||
|
|
||||||
|
private String path;
|
||||||
|
|
||||||
|
private List<String> methods;
|
||||||
|
|
||||||
|
private List<String> contentTypes;
|
||||||
|
|
||||||
|
private List<Service> filters;
|
||||||
|
|
||||||
|
Builder() {
|
||||||
|
this.prefix = "/";
|
||||||
|
this.path = "/**";
|
||||||
|
this.methods = new ArrayList<>();
|
||||||
|
this.contentTypes = new ArrayList<>();
|
||||||
|
this.filters = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setPrefix(String prefix) {
|
||||||
|
this.prefix = prefix;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setPath(String path) {
|
||||||
|
this.path = path;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setMethods(List<String> methods) {
|
||||||
|
this.methods = methods;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder addMethod(String method) {
|
||||||
|
methods.add(method);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setContentTypes(List<String> contentTypes) {
|
||||||
|
this.contentTypes = contentTypes;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder addContentType(String contentType) {
|
||||||
|
this.contentTypes.add(contentType);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setFilters(List<Service> filters) {
|
||||||
|
this.filters = filters;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder addFilter(Service filter) {
|
||||||
|
this.filters.add(filter);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpEndpoint build() {
|
||||||
|
if (methods.isEmpty()) {
|
||||||
|
methods = DEFAULT_METHODS;
|
||||||
|
}
|
||||||
|
return new HttpEndpoint(prefix, path, methods, contentTypes, filters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,13 +2,10 @@ package org.xbib.netty.http.server.endpoint.service;
|
||||||
|
|
||||||
import org.xbib.netty.http.server.ServerRequest;
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
import org.xbib.netty.http.server.ServerResponse;
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
import org.xbib.netty.http.server.endpoint.service.Service;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class EmptyService implements Service {
|
public class EmptyService implements Service {
|
||||||
@Override
|
@Override
|
||||||
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,23 +81,4 @@ public class FileService extends ResourceService {
|
||||||
return length;
|
return length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*@Override
|
|
||||||
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) {
|
|
||||||
String requestPath = serverRequest.getEffectiveRequestPath().substring(1); // always starts with '/'
|
|
||||||
Path path = prefix.resolve(requestPath);
|
|
||||||
if (Files.isReadable(path)) {
|
|
||||||
try (InputStream inputStream = Files.newInputStream(path);
|
|
||||||
ReadableByteChannel byteChannel = Channels.newChannel(inputStream)) {
|
|
||||||
String contentType = MimeTypeUtils.guessFromPath(requestPath, false);
|
|
||||||
serverResponse.write(HttpResponseStatus.OK, contentType, new ChunkedNioStream(byteChannel));
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.log(Level.SEVERE, e.getMessage(), e);
|
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.log(Level.WARNING, "failed to access path " + path + " prefix = " + prefix + " requestPath=" + requestPath);
|
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,10 +34,8 @@ public class MethodService implements Service {
|
||||||
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
||||||
try {
|
try {
|
||||||
m.invoke(obj, serverRequest, serverResponse);
|
m.invoke(obj, serverRequest, serverResponse);
|
||||||
} catch (InvocationTargetException ite) {
|
|
||||||
throw new IOException("error: " + ite.getCause().getMessage());
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IOException("error: " + e);
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import io.netty.handler.codec.http.HttpHeaders;
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
import io.netty.handler.codec.http.HttpMethod;
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
import io.netty.handler.stream.ChunkedNioStream;
|
import io.netty.handler.stream.ChunkedNioStream;
|
||||||
import org.xbib.netty.http.common.util.TimeUtils;
|
import org.xbib.netty.http.common.util.DateTimeUtils;
|
||||||
import org.xbib.netty.http.server.ServerRequest;
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
import org.xbib.netty.http.server.ServerResponse;
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
import org.xbib.netty.http.server.util.MimeTypeUtils;
|
import org.xbib.netty.http.server.util.MimeTypeUtils;
|
||||||
|
@ -49,20 +49,20 @@ public abstract class ResourceService implements Service {
|
||||||
|
|
||||||
protected abstract boolean isRangeResponseEnabled();
|
protected abstract boolean isRangeResponseEnabled();
|
||||||
|
|
||||||
protected void handleResource(ServerRequest serverRequest, ServerResponse serverResponse, Resource resource) {
|
private void handleResource(ServerRequest serverRequest, ServerResponse serverResponse, Resource resource) {
|
||||||
HttpHeaders headers = serverRequest.getRequest().headers();
|
HttpHeaders headers = serverRequest.getHeaders();
|
||||||
String contentType = MimeTypeUtils.guessFromPath(resource.getResourcePath(), false);
|
String contentType = MimeTypeUtils.guessFromPath(resource.getResourcePath(), false);
|
||||||
long maxAgeSeconds = 24 * 3600;
|
long maxAgeSeconds = 24 * 3600;
|
||||||
long expirationMillis = System.currentTimeMillis() + 1000 * maxAgeSeconds;
|
long expirationMillis = System.currentTimeMillis() + 1000 * maxAgeSeconds;
|
||||||
if (isCacheResponseEnabled()) {
|
if (isCacheResponseEnabled()) {
|
||||||
serverResponse.withHeader(HttpHeaderNames.EXPIRES, TimeUtils.formatMillis(expirationMillis))
|
serverResponse.withHeader(HttpHeaderNames.EXPIRES, DateTimeUtils.formatMillis(expirationMillis))
|
||||||
.withHeader(HttpHeaderNames.CACHE_CONTROL, "public, max-age=" + maxAgeSeconds);
|
.withHeader(HttpHeaderNames.CACHE_CONTROL, "public, max-age=" + maxAgeSeconds);
|
||||||
}
|
}
|
||||||
boolean sent = false;
|
boolean sent = false;
|
||||||
if (isETagResponseEnabled()) {
|
if (isETagResponseEnabled()) {
|
||||||
Instant lastModifiedInstant = resource.getLastModified();
|
Instant lastModifiedInstant = resource.getLastModified();
|
||||||
String eTag = resource.getResourcePath().hashCode() + "/" + lastModifiedInstant.toEpochMilli() + "/" + resource.getLength();
|
String eTag = resource.getResourcePath().hashCode() + "/" + lastModifiedInstant.toEpochMilli() + "/" + resource.getLength();
|
||||||
Instant ifUnmodifiedSinceInstant = TimeUtils.parseDate(headers.get(HttpHeaderNames.IF_UNMODIFIED_SINCE));
|
Instant ifUnmodifiedSinceInstant = DateTimeUtils.parseDate(headers.get(HttpHeaderNames.IF_UNMODIFIED_SINCE));
|
||||||
if (ifUnmodifiedSinceInstant != null &&
|
if (ifUnmodifiedSinceInstant != null &&
|
||||||
ifUnmodifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) {
|
ifUnmodifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) {
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.PRECONDITION_FAILED);
|
ServerResponse.write(serverResponse, HttpResponseStatus.PRECONDITION_FAILED);
|
||||||
|
@ -76,20 +76,20 @@ public abstract class ResourceService implements Service {
|
||||||
String ifNoneMatch = headers.get(HttpHeaderNames.IF_NONE_MATCH);
|
String ifNoneMatch = headers.get(HttpHeaderNames.IF_NONE_MATCH);
|
||||||
if (ifNoneMatch != null && matches(ifNoneMatch, eTag)) {
|
if (ifNoneMatch != null && matches(ifNoneMatch, eTag)) {
|
||||||
serverResponse.withHeader(HttpHeaderNames.ETAG, eTag)
|
serverResponse.withHeader(HttpHeaderNames.ETAG, eTag)
|
||||||
.withHeader(HttpHeaderNames.EXPIRES, TimeUtils.formatMillis(expirationMillis));
|
.withHeader(HttpHeaderNames.EXPIRES, DateTimeUtils.formatMillis(expirationMillis));
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_MODIFIED);
|
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_MODIFIED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Instant ifModifiedSinceInstant = TimeUtils.parseDate(headers.get(HttpHeaderNames.IF_MODIFIED_SINCE));
|
Instant ifModifiedSinceInstant = DateTimeUtils.parseDate(headers.get(HttpHeaderNames.IF_MODIFIED_SINCE));
|
||||||
if (ifModifiedSinceInstant != null &&
|
if (ifModifiedSinceInstant != null &&
|
||||||
ifModifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) {
|
ifModifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) {
|
||||||
serverResponse.withHeader(HttpHeaderNames.ETAG, eTag)
|
serverResponse.withHeader(HttpHeaderNames.ETAG, eTag)
|
||||||
.withHeader(HttpHeaderNames.EXPIRES, TimeUtils.formatMillis(expirationMillis));
|
.withHeader(HttpHeaderNames.EXPIRES, DateTimeUtils.formatMillis(expirationMillis));
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_MODIFIED);
|
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_MODIFIED);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
serverResponse.withHeader(HttpHeaderNames.ETAG, eTag)
|
serverResponse.withHeader(HttpHeaderNames.ETAG, eTag)
|
||||||
.withHeader(HttpHeaderNames.LAST_MODIFIED, TimeUtils.formatInstant(lastModifiedInstant));
|
.withHeader(HttpHeaderNames.LAST_MODIFIED, DateTimeUtils.formatInstant(lastModifiedInstant));
|
||||||
if (isRangeResponseEnabled()) {
|
if (isRangeResponseEnabled()) {
|
||||||
performRangeResponse(serverRequest, serverResponse, resource, contentType, eTag, headers);
|
performRangeResponse(serverRequest, serverResponse, resource, contentType, eTag, headers);
|
||||||
sent = true;
|
sent = true;
|
||||||
|
@ -97,11 +97,11 @@ public abstract class ResourceService implements Service {
|
||||||
}
|
}
|
||||||
if (!sent) {
|
if (!sent) {
|
||||||
serverResponse.withHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(resource.getLength()));
|
serverResponse.withHeader(HttpHeaderNames.CONTENT_LENGTH, Long.toString(resource.getLength()));
|
||||||
send(resource.getURL(), HttpResponseStatus.OK, contentType, serverRequest, serverResponse);
|
send(resource.getURL(), contentType, serverRequest, serverResponse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void performRangeResponse(ServerRequest serverRequest, ServerResponse serverResponse,
|
private void performRangeResponse(ServerRequest serverRequest, ServerResponse serverResponse,
|
||||||
Resource resource,
|
Resource resource,
|
||||||
String contentType, String eTag,
|
String contentType, String eTag,
|
||||||
HttpHeaders headers) {
|
HttpHeaders headers) {
|
||||||
|
@ -119,7 +119,7 @@ public abstract class ResourceService implements Service {
|
||||||
String ifRange = headers.get(HttpHeaderNames.IF_RANGE);
|
String ifRange = headers.get(HttpHeaderNames.IF_RANGE);
|
||||||
if (ifRange != null && !ifRange.equals(eTag)) {
|
if (ifRange != null && !ifRange.equals(eTag)) {
|
||||||
try {
|
try {
|
||||||
Instant ifRangeTime = TimeUtils.parseDate(ifRange);
|
Instant ifRangeTime = DateTimeUtils.parseDate(ifRange);
|
||||||
if (ifRangeTime != null && ifRangeTime.plusMillis(1000).isBefore(resource.getLastModified())) {
|
if (ifRangeTime != null && ifRangeTime.plusMillis(1000).isBefore(resource.getLastModified())) {
|
||||||
ranges.add(full);
|
ranges.add(full);
|
||||||
}
|
}
|
||||||
|
@ -180,28 +180,27 @@ public abstract class ResourceService implements Service {
|
||||||
return Arrays.binarySearch(matchValues, toMatch) > -1 || Arrays.binarySearch(matchValues, "*") > -1;
|
return Arrays.binarySearch(matchValues, toMatch) > -1 || Arrays.binarySearch(matchValues, "*") > -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static long sublong(String value, int beginIndex, int endIndex) {
|
private static long sublong(String value, int beginIndex, int endIndex) {
|
||||||
String substring = value.substring(beginIndex, endIndex);
|
String substring = value.substring(beginIndex, endIndex);
|
||||||
return substring.length() > 0 ? Long.parseLong(substring) : -1;
|
return substring.length() > 0 ? Long.parseLong(substring) : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void send(URL url, HttpResponseStatus httpResponseStatus, String contentType,
|
private void send(URL url, String contentType,
|
||||||
ServerRequest serverRequest, ServerResponse serverResponse) {
|
ServerRequest serverRequest, ServerResponse serverResponse) {
|
||||||
if (serverRequest.getRequest().method() == HttpMethod.HEAD) {
|
if (serverRequest.getMethod() == HttpMethod.HEAD) {
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.OK, contentType);
|
ServerResponse.write(serverResponse, HttpResponseStatus.OK, contentType);
|
||||||
} else {
|
} else {
|
||||||
if ("file".equals(url.getProtocol())) {
|
if ("file".equals(url.getProtocol())) {
|
||||||
try {
|
try {
|
||||||
send((FileChannel) Files.newByteChannel(Paths.get(url.toURI())),
|
send((FileChannel) Files.newByteChannel(Paths.get(url.toURI())),
|
||||||
httpResponseStatus, contentType, serverResponse);
|
HttpResponseStatus.OK, contentType, serverResponse);
|
||||||
} catch (URISyntaxException | IOException e) {
|
} catch (URISyntaxException | IOException e) {
|
||||||
logger.log(Level.SEVERE, e.getMessage(), e);
|
logger.log(Level.SEVERE, e.getMessage(), e);
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
|
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try (InputStream inputStream = url.openStream()) {
|
try (InputStream inputStream = url.openStream()) {
|
||||||
send(inputStream, httpResponseStatus, contentType, serverResponse);
|
send(inputStream, HttpResponseStatus.OK, contentType, serverResponse);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.log(Level.SEVERE, e.getMessage(), e);
|
logger.log(Level.SEVERE, e.getMessage(), e);
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
|
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
|
||||||
|
@ -210,9 +209,9 @@ public abstract class ResourceService implements Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void send(URL url, HttpResponseStatus httpResponseStatus, String contentType,
|
private void send(URL url, HttpResponseStatus httpResponseStatus, String contentType,
|
||||||
ServerRequest serverRequest, ServerResponse serverResponse, long offset, long size) {
|
ServerRequest serverRequest, ServerResponse serverResponse, long offset, long size) {
|
||||||
if (serverRequest.getRequest().method() == HttpMethod.HEAD) {
|
if (serverRequest.getMethod() == HttpMethod.HEAD) {
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.OK, contentType);
|
ServerResponse.write(serverResponse, HttpResponseStatus.OK, contentType);
|
||||||
} else {
|
} else {
|
||||||
if ("file".equals(url.getProtocol())) {
|
if ("file".equals(url.getProtocol())) {
|
||||||
|
@ -234,12 +233,12 @@ public abstract class ResourceService implements Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void send(FileChannel fileChannel, HttpResponseStatus httpResponseStatus, String contentType,
|
private void send(FileChannel fileChannel, HttpResponseStatus httpResponseStatus, String contentType,
|
||||||
ServerResponse serverResponse) throws IOException {
|
ServerResponse serverResponse) throws IOException {
|
||||||
send(fileChannel, httpResponseStatus, contentType, serverResponse, 0L, fileChannel.size());
|
send(fileChannel, httpResponseStatus, contentType, serverResponse, 0L, fileChannel.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void send(FileChannel fileChannel, HttpResponseStatus httpResponseStatus, String contentType,
|
private void send(FileChannel fileChannel, HttpResponseStatus httpResponseStatus, String contentType,
|
||||||
ServerResponse serverResponse, long offset, long size) throws IOException {
|
ServerResponse serverResponse, long offset, long size) throws IOException {
|
||||||
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, offset, size);
|
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, offset, size);
|
||||||
serverResponse.withStatus(httpResponseStatus)
|
serverResponse.withStatus(httpResponseStatus)
|
||||||
|
@ -247,7 +246,7 @@ public abstract class ResourceService implements Service {
|
||||||
.write(Unpooled.wrappedBuffer(mappedByteBuffer));
|
.write(Unpooled.wrappedBuffer(mappedByteBuffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType,
|
private void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType,
|
||||||
ServerResponse serverResponse) throws IOException {
|
ServerResponse serverResponse) throws IOException {
|
||||||
try (ReadableByteChannel channel = Channels.newChannel(inputStream)) {
|
try (ReadableByteChannel channel = Channels.newChannel(inputStream)) {
|
||||||
serverResponse.withStatus(httpResponseStatus)
|
serverResponse.withStatus(httpResponseStatus)
|
||||||
|
@ -256,14 +255,14 @@ public abstract class ResourceService implements Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType,
|
private void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType,
|
||||||
ServerResponse serverResponse, long offset, long size) throws IOException {
|
ServerResponse serverResponse, long offset, long size) throws IOException {
|
||||||
serverResponse.withStatus(httpResponseStatus)
|
serverResponse.withStatus(httpResponseStatus)
|
||||||
.withContentType(contentType)
|
.withContentType(contentType)
|
||||||
.write(Unpooled.wrappedBuffer(readBuffer(inputStream, offset, size)));
|
.write(Unpooled.wrappedBuffer(readBuffer(inputStream, offset, size)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static ByteBuffer readBuffer(URL url, long offset, long size) throws IOException, URISyntaxException {
|
private static ByteBuffer readBuffer(URL url, long offset, long size) throws IOException, URISyntaxException {
|
||||||
if ("file".equals(url.getProtocol())) {
|
if ("file".equals(url.getProtocol())) {
|
||||||
try (SeekableByteChannel channel = Files.newByteChannel(Paths.get(url.toURI()))) {
|
try (SeekableByteChannel channel = Files.newByteChannel(Paths.get(url.toURI()))) {
|
||||||
return readBuffer(channel, offset, size);
|
return readBuffer(channel, offset, size);
|
||||||
|
@ -275,17 +274,17 @@ public abstract class ResourceService implements Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static ByteBuffer readBuffer(InputStream inputStream, long offset, long size) throws IOException {
|
private static ByteBuffer readBuffer(InputStream inputStream, long offset, long size) throws IOException {
|
||||||
long n = inputStream.skip(offset);
|
long n = inputStream.skip(offset);
|
||||||
return readBuffer(Channels.newChannel(inputStream), size);
|
return readBuffer(Channels.newChannel(inputStream), size);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static ByteBuffer readBuffer(SeekableByteChannel channel, long offset, long size) throws IOException {
|
private static ByteBuffer readBuffer(SeekableByteChannel channel, long offset, long size) throws IOException {
|
||||||
channel.position(offset);
|
channel.position(offset);
|
||||||
return readBuffer(channel, size);
|
return readBuffer(channel, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static ByteBuffer readBuffer(ReadableByteChannel channel, long size) throws IOException {
|
private static ByteBuffer readBuffer(ReadableByteChannel channel, long size) throws IOException {
|
||||||
ByteBuffer buf = ByteBuffer.allocate((int) size);
|
ByteBuffer buf = ByteBuffer.allocate((int) size);
|
||||||
buf.rewind();
|
buf.rewind();
|
||||||
channel.read(buf);
|
channel.read(buf);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.xbib.netty.http.server.handler.http2;
|
package org.xbib.netty.http.server.handler.http2;
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelHandler;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
import io.netty.channel.ChannelInitializer;
|
import io.netty.channel.ChannelInitializer;
|
||||||
|
@ -69,7 +70,7 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel> {
|
||||||
} else {
|
} else {
|
||||||
configureCleartext(channel);
|
configureCleartext(channel);
|
||||||
}
|
}
|
||||||
if (server.getServerConfig().isDebug()) {
|
if (server.getServerConfig().isDebug() && logger.isLoggable(Level.FINE)) {
|
||||||
logger.log(Level.FINE, "HTTP/2 server channel initialized: " + channel.pipeline().names());
|
logger.log(Level.FINE, "HTTP/2 server channel initialized: " + channel.pipeline().names());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,53 +81,55 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void configureCleartext(Channel ch) {
|
private void configureCleartext(Channel ch) {
|
||||||
ChannelPipeline p = ch.pipeline();
|
ChannelHandler channelHandler = new ChannelInitializer<Channel>() {
|
||||||
Http2MultiplexCodecBuilder serverMultiplexCodecBuilder = Http2MultiplexCodecBuilder.forServer(new ChannelInitializer<Channel>() {
|
|
||||||
@Override
|
@Override
|
||||||
protected void initChannel(Channel channel) {
|
protected void initChannel(Channel channel) {
|
||||||
Transport transport = server.newTransport(httpAddress.getVersion());
|
Transport transport = server.newTransport(httpAddress.getVersion());
|
||||||
channel.attr(Transport.TRANSPORT_ATTRIBUTE_KEY).set(transport);
|
channel.attr(Transport.TRANSPORT_ATTRIBUTE_KEY).set(transport);
|
||||||
ChannelPipeline pipeline = channel.pipeline();
|
ChannelPipeline pipeline = channel.pipeline();
|
||||||
pipeline.addLast("multiplex-server-frame-converter",
|
pipeline.addLast("server-frame-converter",
|
||||||
new Http2StreamFrameToHttpObjectCodec(true));
|
new Http2StreamFrameToHttpObjectCodec(true));
|
||||||
if (serverConfig.isCompressionEnabled()) {
|
if (serverConfig.isCompressionEnabled()) {
|
||||||
pipeline.addLast("multiplex-server-compressor", new HttpContentCompressor());
|
pipeline.addLast("server-compressor", new HttpContentCompressor());
|
||||||
}
|
}
|
||||||
if (serverConfig.isDecompressionEnabled()) {
|
if (serverConfig.isDecompressionEnabled()) {
|
||||||
pipeline.addLast("multiplex-server-decompressor", new HttpContentDecompressor());
|
pipeline.addLast("server-decompressor", new HttpContentDecompressor());
|
||||||
}
|
}
|
||||||
pipeline.addLast("multiplex-server-object-aggregator",
|
pipeline.addLast("server-object-aggregator",
|
||||||
new HttpObjectAggregator(serverConfig.getMaxContentLength()));
|
new HttpObjectAggregator(serverConfig.getMaxContentLength()));
|
||||||
pipeline.addLast("multiplex-server-chunked-write",
|
pipeline.addLast("server-chunked-write", new ChunkedWriteHandler());
|
||||||
new ChunkedWriteHandler());
|
pipeline.addLast("server-request-handler", new ServerRequestHandler());
|
||||||
pipeline.addLast("multiplex-server-request-handler",
|
|
||||||
new ServerRequestHandler());
|
|
||||||
}
|
}
|
||||||
})
|
};
|
||||||
|
Http2MultiplexCodecBuilder multiplexCodecBuilder = Http2MultiplexCodecBuilder.forServer(channelHandler)
|
||||||
.initialSettings(Http2Settings.defaultSettings());
|
.initialSettings(Http2Settings.defaultSettings());
|
||||||
if (serverConfig.isDebug()) {
|
if (serverConfig.isDebug()) {
|
||||||
serverMultiplexCodecBuilder.frameLogger(new Http2FrameLogger(LogLevel.DEBUG, "server"));
|
multiplexCodecBuilder.frameLogger(new Http2FrameLogger(LogLevel.DEBUG, "server"));
|
||||||
}
|
}
|
||||||
Http2MultiplexCodec serverMultiplexCodec = serverMultiplexCodecBuilder.build();
|
Http2MultiplexCodec multiplexCodec = multiplexCodecBuilder.build();
|
||||||
HttpServerUpgradeHandler.UpgradeCodecFactory upgradeCodecFactory = protocol -> {
|
|
||||||
|
HttpServerCodec serverCodec = new HttpServerCodec();
|
||||||
|
HttpServerUpgradeHandler upgradeHandler = new HttpServerUpgradeHandler(serverCodec, protocol -> {
|
||||||
if (AsciiString.contentEquals(Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME, protocol)) {
|
if (AsciiString.contentEquals(Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME, protocol)) {
|
||||||
return new Http2ServerUpgradeCodec("server-codec", serverMultiplexCodec);
|
return new Http2ServerUpgradeCodec(multiplexCodec);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
HttpServerCodec sourceCodec = new HttpServerCodec();
|
|
||||||
HttpServerUpgradeHandler upgradeHandler = new HttpServerUpgradeHandler(sourceCodec, upgradeCodecFactory);
|
|
||||||
CleartextHttp2ServerUpgradeHandler cleartextHttp2ServerUpgradeHandler =
|
CleartextHttp2ServerUpgradeHandler cleartextHttp2ServerUpgradeHandler =
|
||||||
new CleartextHttp2ServerUpgradeHandler(sourceCodec, upgradeHandler, serverMultiplexCodec);
|
new CleartextHttp2ServerUpgradeHandler(serverCodec, upgradeHandler, multiplexCodec);
|
||||||
p.addLast("server-upgrade", cleartextHttp2ServerUpgradeHandler);
|
ChannelPipeline pipeline = ch.pipeline();
|
||||||
p.addLast("server-messages", new ServerMessages());
|
pipeline.addLast("server-upgrade", cleartextHttp2ServerUpgradeHandler);
|
||||||
|
pipeline.addLast("server-messages", new ServerMessages());
|
||||||
}
|
}
|
||||||
|
|
||||||
class ServerRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
|
class ServerRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws IOException {
|
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws IOException {
|
||||||
|
if (server.getServerConfig().isDebug() && logger.isLoggable(Level.FINE)) {
|
||||||
|
logger.log(Level.FINE, "HTTP/2 server pipeline: " + ctx.channel().pipeline().names());
|
||||||
|
}
|
||||||
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get();
|
Transport transport = ctx.channel().attr(Transport.TRANSPORT_ATTRIBUTE_KEY).get();
|
||||||
transport.requestReceived(ctx, fullHttpRequest);
|
transport.requestReceived(ctx, fullHttpRequest);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,6 @@ import io.netty.handler.codec.http2.Http2DataFrame;
|
||||||
import io.netty.handler.codec.http2.Http2Exception;
|
import io.netty.handler.codec.http2.Http2Exception;
|
||||||
import io.netty.handler.codec.http2.Http2Headers;
|
import io.netty.handler.codec.http2.Http2Headers;
|
||||||
import io.netty.handler.codec.http2.Http2HeadersFrame;
|
import io.netty.handler.codec.http2.Http2HeadersFrame;
|
||||||
import io.netty.handler.codec.http2.Http2MultiplexCodec;
|
|
||||||
import io.netty.handler.codec.http2.Http2StreamChannel;
|
import io.netty.handler.codec.http2.Http2StreamChannel;
|
||||||
import io.netty.handler.codec.http2.Http2StreamFrame;
|
import io.netty.handler.codec.http2.Http2StreamFrame;
|
||||||
import io.netty.handler.codec.http2.HttpConversionUtil;
|
import io.netty.handler.codec.http2.HttpConversionUtil;
|
||||||
|
@ -38,13 +37,10 @@ import io.netty.handler.ssl.SslHandler;
|
||||||
import io.netty.util.internal.UnstableApi;
|
import io.netty.util.internal.UnstableApi;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This handler converts from {@link Http2StreamFrame} to {@link HttpObject},
|
* This handler converts from {@link Http2StreamFrame} to {@link HttpObject},
|
||||||
* and back. It can be used as an adapter in conjunction with {@link
|
* and back. It can be used as an adapter to make http/2 connections backward-compatible with
|
||||||
* Http2MultiplexCodec} to make http/2 connections backward-compatible with
|
|
||||||
* {@link ChannelHandler}s expecting {@link HttpObject}.
|
* {@link ChannelHandler}s expecting {@link HttpObject}.
|
||||||
*
|
*
|
||||||
* For simplicity, it converts to chunked encoding unless the entire stream
|
* For simplicity, it converts to chunked encoding unless the entire stream
|
||||||
|
@ -54,8 +50,6 @@ import java.util.logging.Logger;
|
||||||
@Sharable
|
@Sharable
|
||||||
public class Http2StreamFrameToHttpObjectCodec extends MessageToMessageCodec<Http2StreamFrame, HttpObject> {
|
public class Http2StreamFrameToHttpObjectCodec extends MessageToMessageCodec<Http2StreamFrame, HttpObject> {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(Http2StreamFrameToHttpObjectCodec.class.getName());
|
|
||||||
|
|
||||||
private final boolean isServer;
|
private final boolean isServer;
|
||||||
|
|
||||||
private final boolean validateHeaders;
|
private final boolean validateHeaders;
|
||||||
|
|
|
@ -58,6 +58,7 @@ abstract class HttpStreamsHandler<In extends HttpMessage, Out extends HttpMessag
|
||||||
/**
|
/**
|
||||||
* Whether the given incoming message has a body.
|
* Whether the given incoming message has a body.
|
||||||
* @param in input
|
* @param in input
|
||||||
|
* @return true if message has a body
|
||||||
*/
|
*/
|
||||||
protected abstract boolean hasBody(In in);
|
protected abstract boolean hasBody(In in);
|
||||||
|
|
||||||
|
@ -73,6 +74,7 @@ abstract class HttpStreamsHandler<In extends HttpMessage, Out extends HttpMessag
|
||||||
* Create a streamed incoming message with the given stream.
|
* Create a streamed incoming message with the given stream.
|
||||||
* @param in input
|
* @param in input
|
||||||
* @param stream stream
|
* @param stream stream
|
||||||
|
* @return input
|
||||||
*/
|
*/
|
||||||
protected abstract In createStreamedMessage(In in, Publisher<HttpContent> stream);
|
protected abstract In createStreamedMessage(In in, Publisher<HttpContent> stream);
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import io.netty.handler.codec.http.HttpVersion;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.ServerRequest;
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
import org.xbib.netty.http.server.ServerResponse;
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
import org.xbib.netty.http.server.Domain;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
@ -37,14 +37,14 @@ abstract class BaseTransport implements Transport {
|
||||||
* and required special header handling, possibly returning an
|
* and required special header handling, possibly returning an
|
||||||
* appropriate response.
|
* appropriate response.
|
||||||
*
|
*
|
||||||
* @param namedServer the named server
|
* @param domain the named server
|
||||||
* @param serverRequest the request
|
* @param serverRequest the request
|
||||||
* @param serverResponse the response
|
* @param serverResponse the response
|
||||||
* @return whether further processing should be performed
|
* @return whether further processing should be performed
|
||||||
*/
|
*/
|
||||||
static boolean acceptRequest(NamedServer namedServer, ServerRequest serverRequest, ServerResponse serverResponse) {
|
static boolean acceptRequest(Domain domain, ServerRequest serverRequest, ServerResponse serverResponse) {
|
||||||
HttpHeaders reqHeaders = serverRequest.getRequest().headers();
|
HttpHeaders reqHeaders = serverRequest.getHeaders();
|
||||||
HttpVersion version = namedServer.getHttpAddress().getVersion();
|
HttpVersion version = domain.getHttpAddress().getVersion();
|
||||||
if (version.majorVersion() == 1 || version.majorVersion() == 2) {
|
if (version.majorVersion() == 1 || version.majorVersion() == 2) {
|
||||||
if (!reqHeaders.contains(HttpHeaderNames.HOST)) {
|
if (!reqHeaders.contains(HttpHeaderNames.HOST)) {
|
||||||
// RFC2616#14.23: missing Host header gets 400
|
// RFC2616#14.23: missing Host header gets 400
|
||||||
|
@ -74,14 +74,14 @@ abstract class BaseTransport implements Transport {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles a request according to the request method.
|
* Handles a request according to the request method.
|
||||||
* @param namedServer the named server
|
* @param domain the named server
|
||||||
* @param serverRequest the request
|
* @param serverRequest the request
|
||||||
* @param serverResponse the response (into which the response is written)
|
* @param serverResponse the response (into which the response is written)
|
||||||
* @throws IOException if and error occurs
|
* @throws IOException if and error occurs
|
||||||
*/
|
*/
|
||||||
static void handle(NamedServer namedServer, HttpServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
static void handle(Domain domain, HttpServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
|
||||||
// create server URL and parse parameters from query string, path, and parse body, if exists
|
// create server URL and parse parameters from query string, path, and parse body, if exists
|
||||||
serverRequest.createParameters();
|
serverRequest.createParameters();
|
||||||
namedServer.execute(serverRequest, serverResponse);
|
domain.execute(serverRequest, serverResponse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ public class Http2ServerResponse implements ServerResponse {
|
||||||
|
|
||||||
private HttpResponseStatus httpResponseStatus;
|
private HttpResponseStatus httpResponseStatus;
|
||||||
|
|
||||||
public Http2ServerResponse(ServerRequest serverRequest) {
|
public Http2ServerResponse(HttpServerRequest serverRequest) {
|
||||||
Objects.requireNonNull(serverRequest);
|
Objects.requireNonNull(serverRequest);
|
||||||
Objects.requireNonNull(serverRequest.getChannelHandlerContext());
|
Objects.requireNonNull(serverRequest.getChannelHandlerContext());
|
||||||
this.serverRequest = serverRequest;
|
this.serverRequest = serverRequest;
|
||||||
|
@ -109,7 +109,7 @@ public class Http2ServerResponse implements ServerResponse {
|
||||||
if (!headers.contains(HttpHeaderNames.CONTENT_LENGTH) && !headers.contains(HttpHeaderNames.TRANSFER_ENCODING)) {
|
if (!headers.contains(HttpHeaderNames.CONTENT_LENGTH) && !headers.contains(HttpHeaderNames.TRANSFER_ENCODING)) {
|
||||||
headers.add(HttpHeaderNames.CONTENT_LENGTH, Long.toString(byteBuf.readableBytes()));
|
headers.add(HttpHeaderNames.CONTENT_LENGTH, Long.toString(byteBuf.readableBytes()));
|
||||||
}
|
}
|
||||||
if (serverRequest != null && "close".equalsIgnoreCase(serverRequest.getRequest().headers().get(HttpHeaderNames.CONNECTION)) &&
|
if (serverRequest != null && "close".equalsIgnoreCase(serverRequest.getHeaders().get(HttpHeaderNames.CONNECTION)) &&
|
||||||
!headers.contains(HttpHeaderNames.CONNECTION)) {
|
!headers.contains(HttpHeaderNames.CONNECTION)) {
|
||||||
headers.add(HttpHeaderNames.CONNECTION, "close");
|
headers.add(HttpHeaderNames.CONNECTION, "close");
|
||||||
}
|
}
|
||||||
|
@ -162,7 +162,7 @@ public class Http2ServerResponse implements ServerResponse {
|
||||||
logger.log(Level.FINEST, http2HeadersFrame::toString);
|
logger.log(Level.FINEST, http2HeadersFrame::toString);
|
||||||
ctx.channel().write(http2HeadersFrame);
|
ctx.channel().write(http2HeadersFrame);
|
||||||
ChannelFuture channelFuture = ctx.channel().writeAndFlush(new HttpChunkedInput(chunkedInput));
|
ChannelFuture channelFuture = ctx.channel().writeAndFlush(new HttpChunkedInput(chunkedInput));
|
||||||
if ("close".equalsIgnoreCase(serverRequest.getRequest().headers().get(HttpHeaderNames.CONNECTION)) &&
|
if ("close".equalsIgnoreCase(serverRequest.getHeaders().get(HttpHeaderNames.CONNECTION)) &&
|
||||||
!headers.contains(HttpHeaderNames.CONNECTION)) {
|
!headers.contains(HttpHeaderNames.CONNECTION)) {
|
||||||
channelFuture.addListener(ChannelFutureListener.CLOSE);
|
channelFuture.addListener(ChannelFutureListener.CLOSE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import io.netty.handler.codec.http2.Http2Settings;
|
||||||
import io.netty.handler.codec.http2.HttpConversionUtil;
|
import io.netty.handler.codec.http2.HttpConversionUtil;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.ServerResponse;
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
import org.xbib.netty.http.server.Domain;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@ -26,9 +26,9 @@ public class Http2Transport extends BaseTransport {
|
||||||
@Override
|
@Override
|
||||||
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException {
|
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException {
|
||||||
int requestId = requestCounter.incrementAndGet();
|
int requestId = requestCounter.incrementAndGet();
|
||||||
NamedServer namedServer = server.getNamedServer(fullHttpRequest.headers().get(HttpHeaderNames.HOST));
|
Domain domain = server.getNamedServer(fullHttpRequest.headers().get(HttpHeaderNames.HOST));
|
||||||
if (namedServer == null) {
|
if (domain == null) {
|
||||||
namedServer = server.getDefaultNamedServer();
|
domain = server.getDefaultNamedServer();
|
||||||
}
|
}
|
||||||
Integer streamId = fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text());
|
Integer streamId = fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text());
|
||||||
HttpServerRequest serverRequest = new HttpServerRequest();
|
HttpServerRequest serverRequest = new HttpServerRequest();
|
||||||
|
@ -38,8 +38,8 @@ public class Http2Transport extends BaseTransport {
|
||||||
serverRequest.setRequestId(requestId);
|
serverRequest.setRequestId(requestId);
|
||||||
serverRequest.setStreamId(streamId);
|
serverRequest.setStreamId(streamId);
|
||||||
ServerResponse serverResponse = new Http2ServerResponse(serverRequest);
|
ServerResponse serverResponse = new Http2ServerResponse(serverRequest);
|
||||||
if (acceptRequest(namedServer, serverRequest, serverResponse)) {
|
if (acceptRequest(domain, serverRequest, serverResponse)) {
|
||||||
handle(namedServer, serverRequest, serverResponse);
|
handle(domain, serverRequest, serverResponse);
|
||||||
} else {
|
} else {
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE);
|
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,14 @@ package org.xbib.netty.http.server.transport;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.http.FullHttpRequest;
|
import io.netty.handler.codec.http.FullHttpRequest;
|
||||||
|
import io.netty.handler.codec.http.HttpHeaders;
|
||||||
|
import io.netty.handler.codec.http.HttpMethod;
|
||||||
import io.netty.handler.codec.http.HttpUtil;
|
import io.netty.handler.codec.http.HttpUtil;
|
||||||
import io.netty.handler.ssl.SslContext;
|
|
||||||
import org.xbib.net.QueryParameters;
|
import org.xbib.net.QueryParameters;
|
||||||
import org.xbib.net.URL;
|
import org.xbib.net.URL;
|
||||||
import org.xbib.netty.http.common.HttpParameters;
|
import org.xbib.netty.http.common.HttpParameters;
|
||||||
import org.xbib.netty.http.server.ServerRequest;
|
import org.xbib.netty.http.server.ServerRequest;
|
||||||
|
import org.xbib.netty.http.server.endpoint.EndpointInfo;
|
||||||
|
|
||||||
import javax.net.ssl.SSLSession;
|
import javax.net.ssl.SSLSession;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -18,16 +20,12 @@ import java.nio.charset.UnmappableCharacterException;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@code HttpServerRequest} class encapsulates a single request. There must be a default constructor.
|
* The {@code HttpServerRequest} class encapsulates a single request. There must be a default constructor.
|
||||||
*/
|
*/
|
||||||
public class HttpServerRequest implements ServerRequest {
|
public class HttpServerRequest implements ServerRequest {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(HttpServerRequest.class.getName());
|
|
||||||
|
|
||||||
private static final String PATH_SEPARATOR = "/";
|
private static final String PATH_SEPARATOR = "/";
|
||||||
|
|
||||||
private static final CharSequence APPLICATION_FORM_URL_ENCODED = "application/x-www-form-urlencoded";
|
private static final CharSequence APPLICATION_FORM_URL_ENCODED = "application/x-www-form-urlencoded";
|
||||||
|
@ -60,7 +58,6 @@ public class HttpServerRequest implements ServerRequest {
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelHandlerContext getChannelHandlerContext() {
|
public ChannelHandlerContext getChannelHandlerContext() {
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
@ -70,7 +67,6 @@ public class HttpServerRequest implements ServerRequest {
|
||||||
this.info = new EndpointInfo(this);
|
this.info = new EndpointInfo(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public FullHttpRequest getRequest() {
|
public FullHttpRequest getRequest() {
|
||||||
return httpRequest;
|
return httpRequest;
|
||||||
}
|
}
|
||||||
|
@ -120,6 +116,16 @@ public class HttpServerRequest implements ServerRequest {
|
||||||
return pathParameters;
|
return pathParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpMethod getMethod() {
|
||||||
|
return httpRequest.method();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpHeaders getHeaders() {
|
||||||
|
return httpRequest.headers();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createParameters() throws IOException {
|
public void createParameters() throws IOException {
|
||||||
try {
|
try {
|
||||||
|
@ -182,6 +188,11 @@ public class HttpServerRequest implements ServerRequest {
|
||||||
return sslSession;
|
return sslSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf getContent() {
|
||||||
|
return httpRequest.content();
|
||||||
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "ServerRequest[request=" + httpRequest + "]";
|
return "ServerRequest[request=" + httpRequest + "]";
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ public class HttpServerResponse implements ServerResponse {
|
||||||
|
|
||||||
private HttpResponseStatus httpResponseStatus;
|
private HttpResponseStatus httpResponseStatus;
|
||||||
|
|
||||||
public HttpServerResponse(ServerRequest serverRequest) {
|
public HttpServerResponse(HttpServerRequest serverRequest) {
|
||||||
Objects.requireNonNull(serverRequest, "serverRequest");
|
Objects.requireNonNull(serverRequest, "serverRequest");
|
||||||
Objects.requireNonNull(serverRequest.getChannelHandlerContext(), "serverRequest channelHandlerContext");
|
Objects.requireNonNull(serverRequest.getChannelHandlerContext(), "serverRequest channelHandlerContext");
|
||||||
this.serverRequest = serverRequest;
|
this.serverRequest = serverRequest;
|
||||||
|
@ -114,7 +114,7 @@ public class HttpServerResponse implements ServerResponse {
|
||||||
int length = byteBuf.readableBytes();
|
int length = byteBuf.readableBytes();
|
||||||
headers.add(HttpHeaderNames.CONTENT_LENGTH, Long.toString(length));
|
headers.add(HttpHeaderNames.CONTENT_LENGTH, Long.toString(length));
|
||||||
}
|
}
|
||||||
if (serverRequest != null && "close".equalsIgnoreCase(serverRequest.getRequest().headers().get(HttpHeaderNames.CONNECTION)) &&
|
if (serverRequest != null && "close".equalsIgnoreCase(serverRequest.getHeaders().get(HttpHeaderNames.CONNECTION)) &&
|
||||||
!headers.contains(HttpHeaderNames.CONNECTION)) {
|
!headers.contains(HttpHeaderNames.CONNECTION)) {
|
||||||
headers.add(HttpHeaderNames.CONNECTION, "close");
|
headers.add(HttpHeaderNames.CONNECTION, "close");
|
||||||
}
|
}
|
||||||
|
@ -164,7 +164,7 @@ public class HttpServerResponse implements ServerResponse {
|
||||||
logger.log(Level.FINEST, httpResponse.headers()::toString);
|
logger.log(Level.FINEST, httpResponse.headers()::toString);
|
||||||
ctx.channel().write(httpResponse);
|
ctx.channel().write(httpResponse);
|
||||||
ChannelFuture channelFuture = ctx.channel().writeAndFlush(new HttpChunkedInput(chunkedInput));
|
ChannelFuture channelFuture = ctx.channel().writeAndFlush(new HttpChunkedInput(chunkedInput));
|
||||||
if ("close".equalsIgnoreCase(serverRequest.getRequest().headers().get(HttpHeaderNames.CONNECTION)) &&
|
if ("close".equalsIgnoreCase(serverRequest.getHeaders().get(HttpHeaderNames.CONNECTION)) &&
|
||||||
!headers.contains(HttpHeaderNames.CONNECTION)) {
|
!headers.contains(HttpHeaderNames.CONNECTION)) {
|
||||||
channelFuture.addListener(ChannelFutureListener.CLOSE);
|
channelFuture.addListener(ChannelFutureListener.CLOSE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import io.netty.handler.codec.http2.Http2Settings;
|
||||||
import io.netty.handler.ssl.SslHandler;
|
import io.netty.handler.ssl.SslHandler;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.ServerResponse;
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
import org.xbib.netty.http.server.Domain;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@ -27,9 +27,9 @@ public class HttpTransport extends BaseTransport {
|
||||||
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId)
|
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
int requestId = requestCounter.incrementAndGet();
|
int requestId = requestCounter.incrementAndGet();
|
||||||
NamedServer namedServer = server.getNamedServer(fullHttpRequest.headers().get(HttpHeaderNames.HOST));
|
Domain domain = server.getNamedServer(fullHttpRequest.headers().get(HttpHeaderNames.HOST));
|
||||||
if (namedServer == null) {
|
if (domain == null) {
|
||||||
namedServer = server.getDefaultNamedServer();
|
domain = server.getDefaultNamedServer();
|
||||||
}
|
}
|
||||||
HttpServerRequest serverRequest = new HttpServerRequest();
|
HttpServerRequest serverRequest = new HttpServerRequest();
|
||||||
serverRequest.setChannelHandlerContext(ctx);
|
serverRequest.setChannelHandlerContext(ctx);
|
||||||
|
@ -41,8 +41,8 @@ public class HttpTransport extends BaseTransport {
|
||||||
serverRequest.setSession(sslHandler.engine().getSession());
|
serverRequest.setSession(sslHandler.engine().getSession());
|
||||||
}
|
}
|
||||||
HttpServerResponse serverResponse = new HttpServerResponse(serverRequest);
|
HttpServerResponse serverResponse = new HttpServerResponse(serverRequest);
|
||||||
if (acceptRequest(namedServer, serverRequest, serverResponse)) {
|
if (acceptRequest(domain, serverRequest, serverResponse)) {
|
||||||
handle(namedServer, serverRequest, serverResponse);
|
handle(domain, serverRequest, serverResponse);
|
||||||
} else {
|
} else {
|
||||||
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE);
|
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import org.xbib.netty.http.client.Client;
|
||||||
import org.xbib.netty.http.client.Request;
|
import org.xbib.netty.http.client.Request;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
import org.xbib.netty.http.server.Domain;
|
||||||
import org.xbib.netty.http.server.endpoint.service.ClassLoaderService;
|
import org.xbib.netty.http.server.endpoint.service.ClassLoaderService;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -26,11 +26,11 @@ class ClassloaderServiceTest {
|
||||||
@Test
|
@Test
|
||||||
void testSimpleClassloader() throws Exception {
|
void testSimpleClassloader() throws Exception {
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
NamedServer namedServer = NamedServer.builder(httpAddress)
|
Domain domain = Domain.builder(httpAddress)
|
||||||
.singleEndpoint("/classloader", "/**",
|
.singleEndpoint("/classloader", "/**",
|
||||||
new ClassLoaderService(ClassloaderServiceTest.class, "/cl"))
|
new ClassLoaderService(ClassloaderServiceTest.class, "/cl"))
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(namedServer)
|
Server server = Server.builder(domain)
|
||||||
.enableDebug()
|
.enableDebug()
|
||||||
.build();
|
.build();
|
||||||
server.logDiagnostics(Level.INFO);
|
server.logDiagnostics(Level.INFO);
|
||||||
|
|
|
@ -10,7 +10,7 @@ import org.xbib.netty.http.client.listener.ResponseListener;
|
||||||
import org.xbib.netty.http.client.transport.Transport;
|
import org.xbib.netty.http.client.transport.Transport;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
import org.xbib.netty.http.server.Domain;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
@ -30,13 +30,13 @@ class CleartextHttp1Test {
|
||||||
@Test
|
@Test
|
||||||
void testSimpleClearTextHttp1() throws Exception {
|
void testSimpleClearTextHttp1() throws Exception {
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
NamedServer namedServer = NamedServer.builder(httpAddress)
|
Domain domain = Domain.builder(httpAddress)
|
||||||
.singleEndpoint("/**", (request, response) ->
|
.singleEndpoint("/**", (request, response) ->
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.withStatus(HttpResponseStatus.OK)
|
||||||
.withContentType("text/plain")
|
.withContentType("text/plain")
|
||||||
.write(request.getRequest().content().retain()))
|
.write(request.getContent().retain()))
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(namedServer).build();
|
Server server = Server.builder(domain).build();
|
||||||
server.accept();
|
server.accept();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.build();
|
.build();
|
||||||
|
@ -66,13 +66,13 @@ class CleartextHttp1Test {
|
||||||
void testPooledClearTextHttp1() throws Exception {
|
void testPooledClearTextHttp1() throws Exception {
|
||||||
int loop = 4096;
|
int loop = 4096;
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
NamedServer namedServer = NamedServer.builder(httpAddress)
|
Domain domain = Domain.builder(httpAddress)
|
||||||
.singleEndpoint("/**", (request, response) ->
|
.singleEndpoint("/**", (request, response) ->
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.withStatus(HttpResponseStatus.OK)
|
||||||
.withContentType("text/plain")
|
.withContentType("text/plain")
|
||||||
.write(request.getRequest().content().retain()))
|
.write(request.getContent().retain()))
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(namedServer).build();
|
Server server = Server.builder(domain).build();
|
||||||
server.accept();
|
server.accept();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.addPoolNode(httpAddress)
|
.addPoolNode(httpAddress)
|
||||||
|
@ -113,13 +113,13 @@ class CleartextHttp1Test {
|
||||||
int threads = 4;
|
int threads = 4;
|
||||||
int loop = 4 * 1024;
|
int loop = 4 * 1024;
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
NamedServer namedServer = NamedServer.builder(httpAddress)
|
Domain domain = Domain.builder(httpAddress)
|
||||||
.singleEndpoint("/**", (request, response) ->
|
.singleEndpoint("/**", (request, response) ->
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.withStatus(HttpResponseStatus.OK)
|
||||||
.withContentType("text/plain")
|
.withContentType("text/plain")
|
||||||
.write(request.getRequest().content().retain()))
|
.write(request.getContent().retain()))
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(namedServer).build();
|
Server server = Server.builder(domain).build();
|
||||||
server.accept();
|
server.accept();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.addPoolNode(httpAddress)
|
.addPoolNode(httpAddress)
|
||||||
|
|
|
@ -10,7 +10,7 @@ import org.xbib.netty.http.client.transport.Transport;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.ServerResponse;
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
import org.xbib.netty.http.server.Domain;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -31,13 +31,13 @@ class CleartextHttp2Test {
|
||||||
@Test
|
@Test
|
||||||
void testSimpleCleartextHttp2() throws Exception {
|
void testSimpleCleartextHttp2() throws Exception {
|
||||||
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
||||||
NamedServer namedServer = NamedServer.builder(httpAddress)
|
Domain domain = Domain.builder(httpAddress)
|
||||||
.singleEndpoint("/", (request, response) ->
|
.singleEndpoint("/", (request, response) ->
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.withStatus(HttpResponseStatus.OK)
|
||||||
.withContentType("text/plain")
|
.withContentType("text/plain")
|
||||||
.write(request.getRequest().content().retain()))
|
.write(request.getContent().retain()))
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(namedServer).build();
|
Server server = Server.builder(domain).build();
|
||||||
server.accept();
|
server.accept();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.build();
|
.build();
|
||||||
|
@ -73,13 +73,13 @@ class CleartextHttp2Test {
|
||||||
void testPooledClearTextHttp2() throws Exception {
|
void testPooledClearTextHttp2() throws Exception {
|
||||||
int loop = 4096;
|
int loop = 4096;
|
||||||
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
||||||
NamedServer namedServer = NamedServer.builder(httpAddress)
|
Domain domain = Domain.builder(httpAddress)
|
||||||
.singleEndpoint("/", (request, response) ->
|
.singleEndpoint("/", (request, response) ->
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.withStatus(HttpResponseStatus.OK)
|
||||||
.withContentType("text/plain")
|
.withContentType("text/plain")
|
||||||
.write(request.getRequest().content().retain()))
|
.write(request.getContent().retain()))
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(namedServer).build();
|
Server server = Server.builder(domain).build();
|
||||||
server.accept();
|
server.accept();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.addPoolNode(httpAddress)
|
.addPoolNode(httpAddress)
|
||||||
|
@ -123,12 +123,12 @@ class CleartextHttp2Test {
|
||||||
int threads = 2;
|
int threads = 2;
|
||||||
int loop = 2 * 1024;
|
int loop = 2 * 1024;
|
||||||
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
||||||
NamedServer namedServer = NamedServer.builder(httpAddress)
|
Domain domain = Domain.builder(httpAddress)
|
||||||
.singleEndpoint("/", (request, response) ->
|
.singleEndpoint("/", (request, response) ->
|
||||||
ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
|
ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
|
||||||
request.getRequest().content().toString(StandardCharsets.UTF_8)))
|
request.getContent().toString(StandardCharsets.UTF_8)))
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(namedServer).build();
|
Server server = Server.builder(domain).build();
|
||||||
server.accept();
|
server.accept();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.addPoolNode(httpAddress)
|
.addPoolNode(httpAddress)
|
||||||
|
@ -187,25 +187,25 @@ class CleartextHttp2Test {
|
||||||
|
|
||||||
HttpAddress httpAddress1 = HttpAddress.http2("localhost", 8008);
|
HttpAddress httpAddress1 = HttpAddress.http2("localhost", 8008);
|
||||||
AtomicInteger counter1 = new AtomicInteger();
|
AtomicInteger counter1 = new AtomicInteger();
|
||||||
NamedServer namedServer1 = NamedServer.builder(httpAddress1)
|
Domain domain1 = Domain.builder(httpAddress1)
|
||||||
.singleEndpoint("/", (request, response) -> {
|
.singleEndpoint("/", (request, response) -> {
|
||||||
ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
|
ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
|
||||||
request.getRequest().content().toString(StandardCharsets.UTF_8));
|
request.getContent().toString(StandardCharsets.UTF_8));
|
||||||
counter1.incrementAndGet();
|
counter1.incrementAndGet();
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
Server server1 = Server.builder(namedServer1).build();
|
Server server1 = Server.builder(domain1).build();
|
||||||
server1.accept();
|
server1.accept();
|
||||||
HttpAddress httpAddress2 = HttpAddress.http2("localhost", 8009);
|
HttpAddress httpAddress2 = HttpAddress.http2("localhost", 8009);
|
||||||
AtomicInteger counter2 = new AtomicInteger();
|
AtomicInteger counter2 = new AtomicInteger();
|
||||||
NamedServer namedServer2 = NamedServer.builder(httpAddress2)
|
Domain domain2 = Domain.builder(httpAddress2)
|
||||||
.singleEndpoint("/", (request, response) -> {
|
.singleEndpoint("/", (request, response) -> {
|
||||||
ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
|
ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
|
||||||
request.getRequest().content().toString(StandardCharsets.UTF_8));
|
request.getContent().toString(StandardCharsets.UTF_8));
|
||||||
counter2.incrementAndGet();
|
counter2.incrementAndGet();
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
Server server2 = Server.builder(namedServer2).build();
|
Server server2 = Server.builder(domain2).build();
|
||||||
server2.accept();
|
server2.accept();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.addPoolNode(httpAddress1)
|
.addPoolNode(httpAddress1)
|
||||||
|
|
|
@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.ServerResponse;
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
import org.xbib.netty.http.server.Domain;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.BindException;
|
import java.net.BindException;
|
||||||
|
@ -17,11 +17,11 @@ class DoubleServerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDoubleServer() throws IOException {
|
void testDoubleServer() throws IOException {
|
||||||
NamedServer namedServer = NamedServer.builder(HttpAddress.http1("localhost", 8008), "*")
|
Domain domain = Domain.builder(HttpAddress.http1("localhost", 8008), "*")
|
||||||
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World"))
|
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World"))
|
||||||
.build();
|
.build();
|
||||||
Server server1 = Server.builder(namedServer).build();
|
Server server1 = Server.builder(domain).build();
|
||||||
Server server2 = Server.builder(namedServer).build();
|
Server server2 = Server.builder(domain).build();
|
||||||
try {
|
try {
|
||||||
Assertions.assertThrows(BindException.class, () ->{
|
Assertions.assertThrows(BindException.class, () ->{
|
||||||
ChannelFuture channelFuture1 = server1.accept();
|
ChannelFuture channelFuture1 = server1.accept();
|
||||||
|
|
|
@ -9,9 +9,9 @@ import org.xbib.netty.http.client.Request;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.ServerResponse;
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
import org.xbib.netty.http.server.endpoint.Endpoint;
|
import org.xbib.netty.http.server.endpoint.HttpEndpoint;
|
||||||
import org.xbib.netty.http.server.endpoint.EndpointResolver;
|
import org.xbib.netty.http.server.endpoint.EndpointResolver;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
import org.xbib.netty.http.server.Domain;
|
||||||
import org.xbib.netty.http.server.endpoint.service.FileService;
|
import org.xbib.netty.http.server.endpoint.service.FileService;
|
||||||
import org.xbib.netty.http.server.endpoint.service.Service;
|
import org.xbib.netty.http.server.endpoint.service.Service;
|
||||||
|
|
||||||
|
@ -39,16 +39,16 @@ class EndpointTest {
|
||||||
Service service = new FileService(vartmp);
|
Service service = new FileService(vartmp);
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
EndpointResolver endpointResolver = EndpointResolver.builder()
|
EndpointResolver endpointResolver = EndpointResolver.builder()
|
||||||
.addEndpoint(Endpoint.builder().setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPath("/**").build())
|
||||||
.setDispatcher((endpoint, req, resp) -> {
|
.setDispatcher((endpoint, req, resp) -> {
|
||||||
logger.log(Level.FINE, "dispatching endpoint=" + endpoint + " req=" + req);
|
logger.log(Level.FINE, "dispatching endpoint=" + endpoint + " req=" + req);
|
||||||
service.handle(req, resp);
|
service.handle(req, resp);
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
NamedServer namedServer = NamedServer.builder(httpAddress)
|
Domain domain = Domain.builder(httpAddress)
|
||||||
.addEndpointResolver(endpointResolver)
|
.addEndpointResolver(endpointResolver)
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(namedServer)
|
Server server = Server.builder(domain)
|
||||||
.build();
|
.build();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.build();
|
.build();
|
||||||
|
@ -79,16 +79,16 @@ class EndpointTest {
|
||||||
Service service = new FileService(vartmp);
|
Service service = new FileService(vartmp);
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
EndpointResolver endpointResolver = EndpointResolver.builder()
|
EndpointResolver endpointResolver = EndpointResolver.builder()
|
||||||
.addEndpoint(Endpoint.builder().setPrefix("/").setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPrefix("/").setPath("/**").build())
|
||||||
.setDispatcher((endpoint, req, resp) -> {
|
.setDispatcher((endpoint, req, resp) -> {
|
||||||
logger.log(Level.FINE, "dispatching endpoint=" + endpoint + " req=" + req);
|
logger.log(Level.FINE, "dispatching endpoint=" + endpoint + " req=" + req);
|
||||||
service.handle(req, resp);
|
service.handle(req, resp);
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
NamedServer namedServer = NamedServer.builder(httpAddress)
|
Domain domain = Domain.builder(httpAddress)
|
||||||
.addEndpointResolver(endpointResolver)
|
.addEndpointResolver(endpointResolver)
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(namedServer)
|
Server server = Server.builder(domain)
|
||||||
.build();
|
.build();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.build();
|
.build();
|
||||||
|
@ -120,18 +120,18 @@ class EndpointTest {
|
||||||
Service service = new FileService(vartmp);
|
Service service = new FileService(vartmp);
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
EndpointResolver endpointResolver = EndpointResolver.builder()
|
EndpointResolver endpointResolver = EndpointResolver.builder()
|
||||||
.addEndpoint(Endpoint.builder().setPrefix("/static").setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPrefix("/static").setPath("/**").build())
|
||||||
.addEndpoint(Endpoint.builder().setPrefix("/static1").setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPrefix("/static1").setPath("/**").build())
|
||||||
.addEndpoint(Endpoint.builder().setPrefix("/static2").setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPrefix("/static2").setPath("/**").build())
|
||||||
.setDispatcher((endpoint, req, resp) -> {
|
.setDispatcher((endpoint, req, resp) -> {
|
||||||
logger.log(Level.FINE, "dispatching endpoint=" + endpoint + " req=" + req);
|
logger.log(Level.FINE, "dispatching endpoint=" + endpoint + " req=" + req);
|
||||||
service.handle(req, resp);
|
service.handle(req, resp);
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
NamedServer namedServer = NamedServer.builder(httpAddress)
|
Domain domain = Domain.builder(httpAddress)
|
||||||
.addEndpointResolver(endpointResolver)
|
.addEndpointResolver(endpointResolver)
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(namedServer)
|
Server server = Server.builder(domain)
|
||||||
.build();
|
.build();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.build();
|
.build();
|
||||||
|
@ -186,19 +186,19 @@ class EndpointTest {
|
||||||
Service service = new FileService(vartmp);
|
Service service = new FileService(vartmp);
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
EndpointResolver endpointResolver = EndpointResolver.builder()
|
EndpointResolver endpointResolver = EndpointResolver.builder()
|
||||||
.addEndpoint(Endpoint.builder().setPrefix("/static").setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPrefix("/static").setPath("/**").build())
|
||||||
.addEndpoint(Endpoint.builder().setPrefix("/static1").setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPrefix("/static1").setPath("/**").build())
|
||||||
.addEndpoint(Endpoint.builder().setPrefix("/static2").setPath("/**").build())
|
.addEndpoint(HttpEndpoint.builder().setPrefix("/static2").setPath("/**").build())
|
||||||
.setDispatcher((endpoint, req, resp) -> {
|
.setDispatcher((endpoint, req, resp) -> {
|
||||||
logger.log(Level.FINE, "dispatching endpoint=" + endpoint + " req=" + req +
|
logger.log(Level.FINE, "dispatching endpoint=" + endpoint + " req=" + req +
|
||||||
" fragment=" + req.getURL().getFragment());
|
" fragment=" + req.getURL().getFragment());
|
||||||
service.handle(req, resp);
|
service.handle(req, resp);
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
NamedServer namedServer = NamedServer.builder(httpAddress)
|
Domain domain = Domain.builder(httpAddress)
|
||||||
.addEndpointResolver(endpointResolver)
|
.addEndpointResolver(endpointResolver)
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(namedServer)
|
Server server = Server.builder(domain)
|
||||||
.build();
|
.build();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.build();
|
.build();
|
||||||
|
@ -269,15 +269,15 @@ class EndpointTest {
|
||||||
EndpointResolver.Builder endpointResolverBuilder = EndpointResolver.builder()
|
EndpointResolver.Builder endpointResolverBuilder = EndpointResolver.builder()
|
||||||
.setPrefix("/static");
|
.setPrefix("/static");
|
||||||
for (int i = 0; i < max; i++) {
|
for (int i = 0; i < max; i++) {
|
||||||
endpointResolverBuilder.addEndpoint(Endpoint.builder()
|
endpointResolverBuilder.addEndpoint(HttpEndpoint.builder()
|
||||||
.setPath("/" + i + "/**")
|
.setPath("/" + i + "/**")
|
||||||
.addFilter((req, resp) -> ServerResponse.write(resp, HttpResponseStatus.OK))
|
.addFilter((req, resp) -> ServerResponse.write(resp, HttpResponseStatus.OK))
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
NamedServer namedServer = NamedServer.builder(httpAddress)
|
Domain domain = Domain.builder(httpAddress)
|
||||||
.addEndpointResolver(endpointResolverBuilder.build())
|
.addEndpointResolver(endpointResolverBuilder.build())
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(namedServer)
|
Server server = Server.builder(domain)
|
||||||
.build();
|
.build();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -7,7 +7,7 @@ import org.xbib.netty.http.client.Client;
|
||||||
import org.xbib.netty.http.client.Request;
|
import org.xbib.netty.http.client.Request;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
import org.xbib.netty.http.server.Domain;
|
||||||
import org.xbib.netty.http.server.endpoint.service.FileService;
|
import org.xbib.netty.http.server.endpoint.service.FileService;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -30,11 +30,10 @@ class FileServiceTest {
|
||||||
void testFileServiceHttp1() throws Exception {
|
void testFileServiceHttp1() throws Exception {
|
||||||
Path vartmp = Paths.get("/var/tmp/");
|
Path vartmp = Paths.get("/var/tmp/");
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
NamedServer namedServer = NamedServer.builder(httpAddress)
|
Domain domain = Domain.builder(httpAddress)
|
||||||
.singleEndpoint("/static", "/**", new FileService(vartmp))
|
.singleEndpoint("/static", "/**", new FileService(vartmp))
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(namedServer)
|
Server server = Server.builder(domain)
|
||||||
.enableDebug()
|
|
||||||
.build();
|
.build();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.build();
|
.build();
|
||||||
|
@ -65,11 +64,10 @@ class FileServiceTest {
|
||||||
void testFileServiceHttp2() throws Exception {
|
void testFileServiceHttp2() throws Exception {
|
||||||
Path vartmp = Paths.get("/var/tmp/");
|
Path vartmp = Paths.get("/var/tmp/");
|
||||||
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
||||||
NamedServer namedServer = NamedServer.builder(httpAddress)
|
Domain domain = Domain.builder(httpAddress)
|
||||||
.singleEndpoint("/static", "/**", new FileService(vartmp))
|
.singleEndpoint("/static", "/**", new FileService(vartmp))
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(namedServer)
|
Server server = Server.builder(domain)
|
||||||
.enableDebug()
|
|
||||||
.build();
|
.build();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.build();
|
.build();
|
||||||
|
@ -77,7 +75,8 @@ class FileServiceTest {
|
||||||
try {
|
try {
|
||||||
Files.write(vartmp.resolve("test.txt"), "Hello Jörg".getBytes(StandardCharsets.UTF_8));
|
Files.write(vartmp.resolve("test.txt"), "Hello Jörg".getBytes(StandardCharsets.UTF_8));
|
||||||
server.accept();
|
server.accept();
|
||||||
Request request = Request.get().setVersion(HttpVersion.valueOf("HTTP/2.0"))
|
Request request = Request.get()
|
||||||
|
.setVersion(HttpVersion.valueOf("HTTP/2.0"))
|
||||||
.url(server.getServerConfig().getAddress().base().resolve("/static/test.txt"))
|
.url(server.getServerConfig().getAddress().base().resolve("/static/test.txt"))
|
||||||
.build()
|
.build()
|
||||||
.setResponseListener(r -> {
|
.setResponseListener(r -> {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.common.HttpParameters;
|
import org.xbib.netty.http.common.HttpParameters;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.ServerResponse;
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
import org.xbib.netty.http.server.Domain;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
@ -26,14 +26,14 @@ class PostTest {
|
||||||
@Test
|
@Test
|
||||||
void testPostHttp1() throws Exception {
|
void testPostHttp1() throws Exception {
|
||||||
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
|
||||||
NamedServer namedServer = NamedServer.builder(httpAddress)
|
Domain domain = Domain.builder(httpAddress)
|
||||||
.singleEndpoint("/post", "/**", (req, resp) -> {
|
.singleEndpoint("/post", "/**", (req, resp) -> {
|
||||||
HttpParameters parameters = req.getParameters();
|
HttpParameters parameters = req.getParameters();
|
||||||
logger.log(Level.INFO, "got post " + parameters.toString());
|
logger.log(Level.INFO, "got post " + parameters.toString());
|
||||||
ServerResponse.write(resp, HttpResponseStatus.OK);
|
ServerResponse.write(resp, HttpResponseStatus.OK);
|
||||||
}, "POST")
|
}, "POST")
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(namedServer)
|
Server server = Server.builder(domain)
|
||||||
.build();
|
.build();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.build();
|
.build();
|
||||||
|
@ -64,14 +64,14 @@ class PostTest {
|
||||||
@Test
|
@Test
|
||||||
void testPostHttp2() throws Exception {
|
void testPostHttp2() throws Exception {
|
||||||
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
|
||||||
NamedServer namedServer = NamedServer.builder(httpAddress)
|
Domain domain = Domain.builder(httpAddress)
|
||||||
.singleEndpoint("/post", "/**", (req, resp) -> {
|
.singleEndpoint("/post", "/**", (req, resp) -> {
|
||||||
HttpParameters parameters = req.getParameters();
|
HttpParameters parameters = req.getParameters();
|
||||||
logger.log(Level.INFO, "got post " + parameters.toString());
|
logger.log(Level.INFO, "got post " + parameters.toString());
|
||||||
ServerResponse.write(resp, HttpResponseStatus.OK);
|
ServerResponse.write(resp, HttpResponseStatus.OK);
|
||||||
}, "POST")
|
}, "POST")
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(namedServer)
|
Server server = Server.builder(domain)
|
||||||
.build();
|
.build();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
package org.xbib.netty.http.server.test;
|
package org.xbib.netty.http.server.test;
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
import io.netty.handler.codec.http.HttpVersion;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.xbib.netty.http.client.Client;
|
import org.xbib.netty.http.client.Client;
|
||||||
import org.xbib.netty.http.client.Request;
|
import org.xbib.netty.http.client.Request;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
import org.xbib.netty.http.server.Domain;
|
||||||
import org.xbib.netty.http.server.endpoint.service.FileService;
|
import org.xbib.netty.http.server.endpoint.service.FileService;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -26,28 +27,30 @@ class SecureFileServiceTest {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(SecureFileServiceTest.class.getName());
|
private static final Logger logger = Logger.getLogger(SecureFileServiceTest.class.getName());
|
||||||
|
|
||||||
|
@Disabled
|
||||||
@Test
|
@Test
|
||||||
void testSecureFileServerHttp1() throws Exception {
|
void testSecureFileServerHttp1() throws Exception {
|
||||||
Path vartmp = Paths.get("/var/tmp/");
|
Path vartmp = Paths.get("/var/tmp/");
|
||||||
HttpAddress httpAddress = HttpAddress.secureHttp1("localhost", 8143);
|
HttpAddress httpAddress = HttpAddress.secureHttp1("localhost", 8143);
|
||||||
Server server = Server.builder(NamedServer.builder(httpAddress, "*")
|
Server server = Server.builder(Domain.builder(httpAddress, "*")
|
||||||
.setJdkSslProvider()
|
.setJdkSslProvider()
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.singleEndpoint("/static", "/**", new FileService(vartmp))
|
.singleEndpoint("/static", "/**", new FileService(vartmp))
|
||||||
.build())
|
.build())
|
||||||
.setChildThreadCount(8)
|
.setChildThreadCount(8)
|
||||||
.build();
|
.build();
|
||||||
server.logDiagnostics(Level.INFO);
|
//server.logDiagnostics(Level.INFO);
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
.setJdkSslProvider()
|
.setJdkSslProvider()
|
||||||
.trustInsecure()
|
.trustInsecure()
|
||||||
.build();
|
.build();
|
||||||
client.logDiagnostics(Level.INFO);
|
//client.logDiagnostics(Level.INFO);
|
||||||
final AtomicBoolean success = new AtomicBoolean(false);
|
final AtomicBoolean success = new AtomicBoolean(false);
|
||||||
try {
|
try {
|
||||||
Files.write(vartmp.resolve("test.txt"), "Hello Jörg".getBytes(StandardCharsets.UTF_8));
|
Files.write(vartmp.resolve("test.txt"), "Hello Jörg".getBytes(StandardCharsets.UTF_8));
|
||||||
server.accept();
|
server.accept();
|
||||||
Request request = Request.get().setVersion(HttpVersion.HTTP_1_1)
|
Request request = Request.get()
|
||||||
|
.setVersion(HttpVersion.HTTP_1_1)
|
||||||
.url(server.getServerConfig().getAddress().base().resolve("/static/test.txt"))
|
.url(server.getServerConfig().getAddress().base().resolve("/static/test.txt"))
|
||||||
.build()
|
.build()
|
||||||
.setResponseListener(r -> {
|
.setResponseListener(r -> {
|
||||||
|
@ -70,7 +73,52 @@ class SecureFileServiceTest {
|
||||||
void testSecureFileServerHttp2() throws Exception {
|
void testSecureFileServerHttp2() throws Exception {
|
||||||
Path vartmp = Paths.get("/var/tmp/");
|
Path vartmp = Paths.get("/var/tmp/");
|
||||||
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
|
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
|
||||||
Server server = Server.builder(NamedServer.builder(httpAddress, "*")
|
Server server = Server.builder(Domain.builder(httpAddress, "*")
|
||||||
|
.setOpenSSLSslProvider()
|
||||||
|
//.setJdkSslProvider()
|
||||||
|
.setSelfCert()
|
||||||
|
.singleEndpoint("/static", "/**", new FileService(vartmp))
|
||||||
|
.build())
|
||||||
|
.enableDebug()
|
||||||
|
.build();
|
||||||
|
Client client = Client.builder()
|
||||||
|
.setOpenSSLSslProvider()
|
||||||
|
//.setJdkSslProvider()
|
||||||
|
.trustInsecure()
|
||||||
|
.build();
|
||||||
|
final AtomicBoolean success = new AtomicBoolean(false);
|
||||||
|
try {
|
||||||
|
Files.write(vartmp.resolve("test.txt"), "Hello Jörg".getBytes(StandardCharsets.UTF_8));
|
||||||
|
server.accept();
|
||||||
|
Request request = Request.get()
|
||||||
|
.setVersion(HttpVersion.valueOf("HTTP/2.0"))
|
||||||
|
.url(server.getServerConfig().getAddress().base().resolve("/static/test.txt"))
|
||||||
|
.build()
|
||||||
|
.setResponseListener(r -> {
|
||||||
|
assertEquals("Hello Jörg", r.content().toString(StandardCharsets.UTF_8));
|
||||||
|
success.set(true);
|
||||||
|
});
|
||||||
|
logger.log(Level.INFO, request.toString());
|
||||||
|
client.execute(request).get();
|
||||||
|
logger.log(Level.INFO, "request complete");
|
||||||
|
} finally {
|
||||||
|
server.shutdownGracefully();
|
||||||
|
client.shutdownGracefully();
|
||||||
|
Files.delete(vartmp.resolve("test.txt"));
|
||||||
|
logger.log(Level.INFO, "server and client shut down");
|
||||||
|
}
|
||||||
|
assertTrue(success.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect HTTP 1.1 client to a HTTP 2.0 server.
|
||||||
|
*/
|
||||||
|
@Disabled
|
||||||
|
@Test
|
||||||
|
void testSecureFileServerMixHttp1Http2() throws Exception {
|
||||||
|
Path vartmp = Paths.get("/var/tmp/");
|
||||||
|
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
|
||||||
|
Server server = Server.builder(Domain.builder(httpAddress, "*")
|
||||||
.setOpenSSLSslProvider()
|
.setOpenSSLSslProvider()
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.singleEndpoint("/static", "/**", new FileService(vartmp))
|
.singleEndpoint("/static", "/**", new FileService(vartmp))
|
||||||
|
@ -84,7 +132,8 @@ class SecureFileServiceTest {
|
||||||
try {
|
try {
|
||||||
Files.write(vartmp.resolve("test.txt"), "Hello Jörg".getBytes(StandardCharsets.UTF_8));
|
Files.write(vartmp.resolve("test.txt"), "Hello Jörg".getBytes(StandardCharsets.UTF_8));
|
||||||
server.accept();
|
server.accept();
|
||||||
Request request = Request.get().setVersion(HttpVersion.valueOf("HTTP/2.0"))
|
Request request = Request.get()
|
||||||
|
.setVersion(HttpVersion.HTTP_1_1)
|
||||||
.url(server.getServerConfig().getAddress().base().resolve("/static/test.txt"))
|
.url(server.getServerConfig().getAddress().base().resolve("/static/test.txt"))
|
||||||
.build()
|
.build()
|
||||||
.setResponseListener(r -> {
|
.setResponseListener(r -> {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import org.xbib.netty.http.client.listener.ResponseListener;
|
||||||
import org.xbib.netty.http.client.transport.Transport;
|
import org.xbib.netty.http.client.transport.Transport;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
import org.xbib.netty.http.server.Domain;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -31,12 +31,12 @@ class SecureHttp1Test {
|
||||||
@Test
|
@Test
|
||||||
void testSimpleSecureHttp1() throws Exception {
|
void testSimpleSecureHttp1() throws Exception {
|
||||||
HttpAddress httpAddress = HttpAddress.secureHttp1("localhost", 8143);
|
HttpAddress httpAddress = HttpAddress.secureHttp1("localhost", 8143);
|
||||||
Server server = Server.builder(NamedServer.builder(httpAddress)
|
Server server = Server.builder(Domain.builder(httpAddress)
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.singleEndpoint("/", (request, response) ->
|
.singleEndpoint("/", (request, response) ->
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.withStatus(HttpResponseStatus.OK)
|
||||||
.withContentType("text/plain")
|
.withContentType("text/plain")
|
||||||
.write(request.getRequest().content().retain()))
|
.write(request.getContent().retain()))
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
Client client = Client.builder()
|
Client client = Client.builder()
|
||||||
|
@ -66,12 +66,12 @@ class SecureHttp1Test {
|
||||||
void testPooledSecureHttp1() throws Exception {
|
void testPooledSecureHttp1() throws Exception {
|
||||||
int loop = 4096;
|
int loop = 4096;
|
||||||
HttpAddress httpAddress = HttpAddress.secureHttp1("localhost", 8143);
|
HttpAddress httpAddress = HttpAddress.secureHttp1("localhost", 8143);
|
||||||
Server server = Server.builder(NamedServer.builder(httpAddress)
|
Server server = Server.builder(Domain.builder(httpAddress)
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.singleEndpoint("/", (request, response) ->
|
.singleEndpoint("/", (request, response) ->
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.withStatus(HttpResponseStatus.OK)
|
||||||
.withContentType("text/plain")
|
.withContentType("text/plain")
|
||||||
.write(request.getRequest().content().retain()))
|
.write(request.getContent().retain()))
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
server.accept();
|
server.accept();
|
||||||
|
@ -114,12 +114,12 @@ class SecureHttp1Test {
|
||||||
int threads = 4;
|
int threads = 4;
|
||||||
int loop = 4 * 1024;
|
int loop = 4 * 1024;
|
||||||
HttpAddress httpAddress = HttpAddress.secureHttp1("localhost", 8143);
|
HttpAddress httpAddress = HttpAddress.secureHttp1("localhost", 8143);
|
||||||
Server server = Server.builder(NamedServer.builder(httpAddress)
|
Server server = Server.builder(Domain.builder(httpAddress)
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.singleEndpoint("/", (request, response) ->
|
.singleEndpoint("/", (request, response) ->
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.withStatus(HttpResponseStatus.OK)
|
||||||
.withContentType("text/plain")
|
.withContentType("text/plain")
|
||||||
.write(request.getRequest().content().retain()))
|
.write(request.getContent().retain()))
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
server.accept();
|
server.accept();
|
||||||
|
|
|
@ -9,7 +9,7 @@ import org.xbib.netty.http.client.listener.ResponseListener;
|
||||||
import org.xbib.netty.http.client.transport.Transport;
|
import org.xbib.netty.http.client.transport.Transport;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
import org.xbib.netty.http.server.Domain;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -30,12 +30,12 @@ class SecureHttp2Test {
|
||||||
@Test
|
@Test
|
||||||
void testSimpleSecureHttp2() throws Exception {
|
void testSimpleSecureHttp2() throws Exception {
|
||||||
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
|
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
|
||||||
Server server = Server.builder(NamedServer.builder(httpAddress)
|
Server server = Server.builder(Domain.builder(httpAddress)
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.singleEndpoint("/", (request, response) ->
|
.singleEndpoint("/", (request, response) ->
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.withStatus(HttpResponseStatus.OK)
|
||||||
.withContentType("text/plain")
|
.withContentType("text/plain")
|
||||||
.write(request.getRequest().content().retain()))
|
.write(request.getContent().retain()))
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
server.accept();
|
server.accept();
|
||||||
|
@ -73,12 +73,12 @@ class SecureHttp2Test {
|
||||||
void testPooledSecureHttp2() throws Exception {
|
void testPooledSecureHttp2() throws Exception {
|
||||||
int loop = 4096;
|
int loop = 4096;
|
||||||
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
|
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
|
||||||
Server server = Server.builder(NamedServer.builder(httpAddress)
|
Server server = Server.builder(Domain.builder(httpAddress)
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.singleEndpoint("/", (request, response) ->
|
.singleEndpoint("/", (request, response) ->
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.withStatus(HttpResponseStatus.OK)
|
||||||
.withContentType("text/plain")
|
.withContentType("text/plain")
|
||||||
.write(request.getRequest().content().retain()))
|
.write(request.getContent().retain()))
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
server.accept();
|
server.accept();
|
||||||
|
@ -124,12 +124,12 @@ class SecureHttp2Test {
|
||||||
int threads = 4;
|
int threads = 4;
|
||||||
int loop = 4 * 1024;
|
int loop = 4 * 1024;
|
||||||
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
|
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
|
||||||
Server server = Server.builder(NamedServer.builder(httpAddress)
|
Server server = Server.builder(Domain.builder(httpAddress)
|
||||||
.setSelfCert()
|
.setSelfCert()
|
||||||
.singleEndpoint("/", (request, response) ->
|
.singleEndpoint("/", (request, response) ->
|
||||||
response.withStatus(HttpResponseStatus.OK)
|
response.withStatus(HttpResponseStatus.OK)
|
||||||
.withContentType("text/plain")
|
.withContentType("text/plain")
|
||||||
.write(request.getRequest().content().retain())
|
.write(request.getContent().retain())
|
||||||
)
|
)
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -5,17 +5,17 @@ import org.junit.jupiter.api.Test;
|
||||||
import org.xbib.netty.http.common.HttpAddress;
|
import org.xbib.netty.http.common.HttpAddress;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.ServerResponse;
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
import org.xbib.netty.http.server.Domain;
|
||||||
|
|
||||||
@Disabled
|
@Disabled
|
||||||
class ServerTest {
|
class ServerTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testServer() throws Exception {
|
void testServer() throws Exception {
|
||||||
NamedServer namedServer = NamedServer.builder(HttpAddress.http1("localhost", 8008), "*")
|
Domain domain = Domain.builder(HttpAddress.http1("localhost", 8008), "*")
|
||||||
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World"))
|
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World"))
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(namedServer).build();
|
Server server = Server.builder(domain).build();
|
||||||
try {
|
try {
|
||||||
server.accept().channel().closeFuture().sync();
|
server.accept().channel().closeFuture().sync();
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import org.junit.jupiter.api.TestInstance;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.xbib.netty.http.server.Server;
|
import org.xbib.netty.http.server.Server;
|
||||||
import org.xbib.netty.http.server.ServerResponse;
|
import org.xbib.netty.http.server.ServerResponse;
|
||||||
import org.xbib.netty.http.server.endpoint.NamedServer;
|
import org.xbib.netty.http.server.Domain;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -22,10 +22,10 @@ class ThreadLeakTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testForLeaks() throws IOException {
|
void testForLeaks() throws IOException {
|
||||||
NamedServer namedServer = NamedServer.builder()
|
Domain domain = Domain.builder()
|
||||||
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World"))
|
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World"))
|
||||||
.build();
|
.build();
|
||||||
Server server = Server.builder(namedServer)
|
Server server = Server.builder(domain)
|
||||||
.setByteBufAllocator(UnpooledByteBufAllocator.DEFAULT)
|
.setByteBufAllocator(UnpooledByteBufAllocator.DEFAULT)
|
||||||
.build();
|
.build();
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -0,0 +1,170 @@
|
||||||
|
package org.xbib.netty.http.server.test.cookie;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.xbib.netty.http.common.cookie.Cookie;
|
||||||
|
import org.xbib.netty.http.server.cookie.ServerCookieDecoder;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class ServerCookieDecoderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodingSingleCookie() {
|
||||||
|
String cookieString = "myCookie=myValue";
|
||||||
|
Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode(cookieString);
|
||||||
|
assertEquals(1, cookies.size());
|
||||||
|
Cookie cookie = cookies.iterator().next();
|
||||||
|
assertNotNull(cookie);
|
||||||
|
assertEquals("myValue", cookie.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodingMultipleCookies() {
|
||||||
|
String c1 = "myCookie=myValue;";
|
||||||
|
String c2 = "myCookie2=myValue2;";
|
||||||
|
String c3 = "myCookie3=myValue3;";
|
||||||
|
Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode(c1 + c2 + c3);
|
||||||
|
assertEquals(3, cookies.size());
|
||||||
|
Iterator<Cookie> it = cookies.iterator();
|
||||||
|
Cookie cookie = it.next();
|
||||||
|
assertNotNull(cookie);
|
||||||
|
assertEquals("myValue", cookie.value());
|
||||||
|
cookie = it.next();
|
||||||
|
assertNotNull(cookie);
|
||||||
|
assertEquals("myValue2", cookie.value());
|
||||||
|
cookie = it.next();
|
||||||
|
assertNotNull(cookie);
|
||||||
|
assertEquals("myValue3", cookie.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodingGoogleAnalyticsCookie() {
|
||||||
|
String source =
|
||||||
|
"ARPT=LWUKQPSWRTUN04CKKJI; " +
|
||||||
|
"kw-2E343B92-B097-442c-BFA5-BE371E0325A2=unfinished_furniture; " +
|
||||||
|
"__utma=48461872.1094088325.1258140131.1258140131.1258140131.1; " +
|
||||||
|
"__utmb=48461872.13.10.1258140131; __utmc=48461872; " +
|
||||||
|
"__utmz=48461872.1258140131.1.1.utmcsr=overstock.com|utmccn=(referral)|" +
|
||||||
|
"utmcmd=referral|utmcct=/Home-Garden/Furniture/Clearance/clearance/32/dept.html";
|
||||||
|
Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode(source);
|
||||||
|
Iterator<Cookie> it = cookies.iterator();
|
||||||
|
Cookie c;
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("ARPT", c.name());
|
||||||
|
assertEquals("LWUKQPSWRTUN04CKKJI", c.value());
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("kw-2E343B92-B097-442c-BFA5-BE371E0325A2", c.name());
|
||||||
|
assertEquals("unfinished_furniture", c.value());
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("__utma", c.name());
|
||||||
|
assertEquals("48461872.1094088325.1258140131.1258140131.1258140131.1", c.value());
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("__utmb", c.name());
|
||||||
|
assertEquals("48461872.13.10.1258140131", c.value());
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("__utmc", c.name());
|
||||||
|
assertEquals("48461872", c.value());
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("__utmz", c.name());
|
||||||
|
assertEquals("48461872.1258140131.1.1.utmcsr=overstock.com|" +
|
||||||
|
"utmccn=(referral)|utmcmd=referral|utmcct=/Home-Garden/Furniture/Clearance/clearance/32/dept.html",
|
||||||
|
c.value());
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodingLongValue() {
|
||||||
|
String longValue = "b___$Q__$ha__<NC=MN(F__%#4__<NC=MN(F__2_d____#=IvZB__2_F____'=KqtH__2-9____" +
|
||||||
|
"'=IvZM__3f:____$=HbQW__3g'____%=J^wI__3g-____%=J^wI__3g1____$=HbQW__3g2____" +
|
||||||
|
"$=HbQW__3g5____%=J^wI__3g9____$=HbQW__3gT____$=HbQW__3gX____#=J^wI__3gY____" +
|
||||||
|
"#=J^wI__3gh____$=HbQW__3gj____$=HbQW__3gr____$=HbQW__3gx____#=J^wI__3h_____" +
|
||||||
|
"$=HbQW__3h$____#=J^wI__3h'____$=HbQW__3h_____$=HbQW__3h0____%=J^wI__3h1____" +
|
||||||
|
"#=J^wI__3h2____$=HbQW__3h4____$=HbQW__3h7____$=HbQW__3h8____%=J^wI__3h:____" +
|
||||||
|
"#=J^wI__3h@____%=J^wI__3hB____$=HbQW__3hC____$=HbQW__3hL____$=HbQW__3hQ____" +
|
||||||
|
"$=HbQW__3hS____%=J^wI__3hU____$=HbQW__3h[____$=HbQW__3h^____$=HbQW__3hd____" +
|
||||||
|
"%=J^wI__3he____%=J^wI__3hf____%=J^wI__3hg____$=HbQW__3hh____%=J^wI__3hi____" +
|
||||||
|
"%=J^wI__3hv____$=HbQW__3i/____#=J^wI__3i2____#=J^wI__3i3____%=J^wI__3i4____" +
|
||||||
|
"$=HbQW__3i7____$=HbQW__3i8____$=HbQW__3i9____%=J^wI__3i=____#=J^wI__3i>____" +
|
||||||
|
"%=J^wI__3iD____$=HbQW__3iF____#=J^wI__3iH____%=J^wI__3iM____%=J^wI__3iS____" +
|
||||||
|
"#=J^wI__3iU____%=J^wI__3iZ____#=J^wI__3i]____%=J^wI__3ig____%=J^wI__3ij____" +
|
||||||
|
"%=J^wI__3ik____#=J^wI__3il____$=HbQW__3in____%=J^wI__3ip____$=HbQW__3iq____" +
|
||||||
|
"$=HbQW__3it____%=J^wI__3ix____#=J^wI__3j_____$=HbQW__3j%____$=HbQW__3j'____" +
|
||||||
|
"%=J^wI__3j(____%=J^wI__9mJ____'=KqtH__=SE__<NC=MN(F__?VS__<NC=MN(F__Zw`____" +
|
||||||
|
"%=KqtH__j+C__<NC=MN(F__j+M__<NC=MN(F__j+a__<NC=MN(F__j_.__<NC=MN(F__n>M____" +
|
||||||
|
"'=KqtH__s1X____$=MMyc__s1_____#=MN#O__ypn____'=KqtH__ypr____'=KqtH_#%h_____" +
|
||||||
|
"%=KqtH_#%o_____'=KqtH_#)H6__<NC=MN(F_#*%'____%=KqtH_#+k(____'=KqtH_#-E_____" +
|
||||||
|
"'=KqtH_#1)w____'=KqtH_#1)y____'=KqtH_#1*M____#=KqtH_#1*p____'=KqtH_#14Q__<N" +
|
||||||
|
"C=MN(F_#14S__<NC=MN(F_#16I__<NC=MN(F_#16N__<NC=MN(F_#16X__<NC=MN(F_#16k__<N" +
|
||||||
|
"C=MN(F_#17@__<NC=MN(F_#17A__<NC=MN(F_#1Cq____'=KqtH_#7)_____#=KqtH_#7)b____" +
|
||||||
|
"#=KqtH_#7Ww____'=KqtH_#?cQ____'=KqtH_#His____'=KqtH_#Jrh____'=KqtH_#O@M__<N" +
|
||||||
|
"C=MN(F_#O@O__<NC=MN(F_#OC6__<NC=MN(F_#Os.____#=KqtH_#YOW____#=H/Li_#Zat____" +
|
||||||
|
"'=KqtH_#ZbI____%=KqtH_#Zbc____'=KqtH_#Zbs____%=KqtH_#Zby____'=KqtH_#Zce____" +
|
||||||
|
"'=KqtH_#Zdc____%=KqtH_#Zea____'=KqtH_#ZhI____#=KqtH_#ZiD____'=KqtH_#Zis____" +
|
||||||
|
"'=KqtH_#Zj0____#=KqtH_#Zj1____'=KqtH_#Zj[____'=KqtH_#Zj]____'=KqtH_#Zj^____" +
|
||||||
|
"'=KqtH_#Zjb____'=KqtH_#Zk_____'=KqtH_#Zk6____#=KqtH_#Zk9____%=KqtH_#Zk<____" +
|
||||||
|
"'=KqtH_#Zl>____'=KqtH_#]9R____$=H/Lt_#]I6____#=KqtH_#]Z#____%=KqtH_#^*N____" +
|
||||||
|
"#=KqtH_#^:m____#=KqtH_#_*_____%=J^wI_#`-7____#=KqtH_#`T>____'=KqtH_#`T?____" +
|
||||||
|
"'=KqtH_#`TA____'=KqtH_#`TB____'=KqtH_#`TG____'=KqtH_#`TP____#=KqtH_#`U_____" +
|
||||||
|
"'=KqtH_#`U/____'=KqtH_#`U0____#=KqtH_#`U9____'=KqtH_#aEQ____%=KqtH_#b<)____" +
|
||||||
|
"'=KqtH_#c9-____%=KqtH_#dxC____%=KqtH_#dxE____%=KqtH_#ev$____'=KqtH_#fBi____" +
|
||||||
|
"#=KqtH_#fBj____'=KqtH_#fG)____'=KqtH_#fG+____'=KqtH_#g<d____'=KqtH_#g<e____" +
|
||||||
|
"'=KqtH_#g=J____'=KqtH_#gat____#=KqtH_#s`D____#=J_#p_#sg?____#=J_#p_#t<a____" +
|
||||||
|
"#=KqtH_#t<c____#=KqtH_#trY____$=JiYj_#vA$____'=KqtH_#xs_____'=KqtH_$$rO____" +
|
||||||
|
"#=KqtH_$$rP____#=KqtH_$(_%____'=KqtH_$)]o____%=KqtH_$_@)____'=KqtH_$_k]____" +
|
||||||
|
"'=KqtH_$1]+____%=KqtH_$3IO____%=KqtH_$3J#____'=KqtH_$3J.____'=KqtH_$3J:____" +
|
||||||
|
"#=KqtH_$3JH____#=KqtH_$3JI____#=KqtH_$3JK____%=KqtH_$3JL____'=KqtH_$3JS____" +
|
||||||
|
"'=KqtH_$8+M____#=KqtH_$99d____%=KqtH_$:Lw____#=LK+x_$:N@____#=KqtG_$:NC____" +
|
||||||
|
"#=KqtG_$:hW____'=KqtH_$:i[____'=KqtH_$:ih____'=KqtH_$:it____'=KqtH_$:kO____" +
|
||||||
|
"'=KqtH_$>*B____'=KqtH_$>hD____+=J^x0_$?lW____'=KqtH_$?ll____'=KqtH_$?lm____" +
|
||||||
|
"%=KqtH_$?mi____'=KqtH_$?mx____'=KqtH_$D7]____#=J_#p_$D@T____#=J_#p_$V<g____" +
|
||||||
|
"'=KqtH";
|
||||||
|
Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode("bh=\"" + longValue + "\";");
|
||||||
|
assertEquals(1, cookies.size());
|
||||||
|
Cookie c = cookies.iterator().next();
|
||||||
|
assertEquals("bh", c.name());
|
||||||
|
assertEquals(longValue, c.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDecodingOldRFC2965Cookies() {
|
||||||
|
String source = "$Version=\"1\"; " +
|
||||||
|
"Part_Number1=\"Riding_Rocket_0023\"; $Path=\"/acme/ammo\"; " +
|
||||||
|
"Part_Number2=\"Rocket_Launcher_0001\"; $Path=\"/acme\"";
|
||||||
|
Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode(source);
|
||||||
|
Iterator<Cookie> it = cookies.iterator();
|
||||||
|
Cookie c;
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("Part_Number1", c.name());
|
||||||
|
assertEquals("Riding_Rocket_0023", c.value());
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("Part_Number2", c.name());
|
||||||
|
assertEquals("Rocket_Launcher_0001", c.value());
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRejectCookieValueWithSemicolon() {
|
||||||
|
Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode("name=\"foo;bar\";");
|
||||||
|
assertTrue(cookies.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCaseSensitiveNames() {
|
||||||
|
Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode("session_id=a; Session_id=b;");
|
||||||
|
Iterator<Cookie> it = cookies.iterator();
|
||||||
|
Cookie c;
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("session_id", c.name());
|
||||||
|
assertEquals("a", c.value());
|
||||||
|
c = it.next();
|
||||||
|
assertEquals("Session_id", c.name());
|
||||||
|
assertEquals("b", c.value());
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
package org.xbib.netty.http.server.test.cookie;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.xbib.netty.http.client.cookie.ClientCookieEncoder;
|
||||||
|
import org.xbib.netty.http.common.cookie.Cookie;
|
||||||
|
import org.xbib.netty.http.common.cookie.DefaultCookie;
|
||||||
|
import org.xbib.netty.http.common.util.DateTimeUtils;
|
||||||
|
import org.xbib.netty.http.server.cookie.ServerCookieEncoder;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class ServerCookieEncoderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEncodingSingleCookieV0() {
|
||||||
|
int maxAge = 50;
|
||||||
|
String result = "myCookie=myValue; Max-Age=50; Expires=(.+?); Path=/apathsomewhere; Domain=.adomainsomewhere; Secure";
|
||||||
|
Cookie cookie = new DefaultCookie("myCookie", "myValue");
|
||||||
|
cookie.setDomain(".adomainsomewhere");
|
||||||
|
cookie.setMaxAge(maxAge);
|
||||||
|
cookie.setPath("/apathsomewhere");
|
||||||
|
cookie.setSecure(true);
|
||||||
|
String encodedCookie = ServerCookieEncoder.STRICT.encode(cookie);
|
||||||
|
Matcher matcher = Pattern.compile(result).matcher(encodedCookie);
|
||||||
|
assertTrue(matcher.find());
|
||||||
|
Instant expire = DateTimeUtils.parseDate(matcher.group(1));
|
||||||
|
long diff = (expire.toEpochMilli() - System.currentTimeMillis()) / 1000;
|
||||||
|
assertTrue(Math.abs(diff - maxAge) <= 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEncodingWithNoCookies() {
|
||||||
|
String encodedCookie1 = ClientCookieEncoder.STRICT.encode(Collections.emptyList());
|
||||||
|
List<String> encodedCookie2 = ServerCookieEncoder.STRICT.encode(Collections.emptyList());
|
||||||
|
assertNull(encodedCookie1);
|
||||||
|
assertNotNull(encodedCookie2);
|
||||||
|
assertTrue(encodedCookie2.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEncodingMultipleCookiesStrict() {
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
result.add("cookie2=value2");
|
||||||
|
result.add("cookie1=value3");
|
||||||
|
Cookie cookie1 = new DefaultCookie("cookie1", "value1");
|
||||||
|
Cookie cookie2 = new DefaultCookie("cookie2", "value2");
|
||||||
|
Cookie cookie3 = new DefaultCookie("cookie1", "value3");
|
||||||
|
List<String> encodedCookies = ServerCookieEncoder.STRICT.encode(cookie1, cookie2, cookie3);
|
||||||
|
assertEquals(result, encodedCookies);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void illegalCharInCookieNameMakesStrictEncoderThrowsException() {
|
||||||
|
Set<Character> illegalChars = new LinkedHashSet<>();
|
||||||
|
// CTLs
|
||||||
|
for (char i = 0x00; i <= 0x1F; i++) {
|
||||||
|
illegalChars.add(i);
|
||||||
|
}
|
||||||
|
illegalChars.add((char) 0x7F);
|
||||||
|
// separators
|
||||||
|
for (char c : new char[] {'(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']',
|
||||||
|
'?', '=', '{', '}', ' ', '\t' }) {
|
||||||
|
illegalChars.add(c);
|
||||||
|
}
|
||||||
|
int exceptions = 0;
|
||||||
|
for (char c : illegalChars) {
|
||||||
|
try {
|
||||||
|
ServerCookieEncoder.STRICT.encode(new DefaultCookie("foo" + c + "bar", "value"));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
exceptions++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(illegalChars.size(), exceptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void illegalCharInCookieValueMakesStrictEncoderThrowsException() {
|
||||||
|
Set<Character> illegalChars = new LinkedHashSet<>();
|
||||||
|
// CTLs
|
||||||
|
for (char i = 0x00; i <= 0x1F; i++) {
|
||||||
|
illegalChars.add(i);
|
||||||
|
}
|
||||||
|
illegalChars.add((char) 0x7F);
|
||||||
|
// whitespace, DQUOTE, comma, semicolon, and backslash
|
||||||
|
for (char c : new char[] { ' ', '"', ',', ';', '\\' }) {
|
||||||
|
illegalChars.add(c);
|
||||||
|
}
|
||||||
|
int exceptions = 0;
|
||||||
|
for (char c : illegalChars) {
|
||||||
|
try {
|
||||||
|
ServerCookieEncoder.STRICT.encode(new DefaultCookie("name", "value" + c));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
exceptions++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(illegalChars.size(), exceptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEncodingMultipleCookiesLax() {
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
result.add("cookie1=value1");
|
||||||
|
result.add("cookie2=value2");
|
||||||
|
result.add("cookie1=value3");
|
||||||
|
Cookie cookie1 = new DefaultCookie("cookie1", "value1");
|
||||||
|
Cookie cookie2 = new DefaultCookie("cookie2", "value2");
|
||||||
|
Cookie cookie3 = new DefaultCookie("cookie1", "value3");
|
||||||
|
List<String> encodedCookies = ServerCookieEncoder.LAX.encode(cookie1, cookie2, cookie3);
|
||||||
|
assertEquals(result, encodedCookies);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link ServerStreamConnection}
|
||||||
|
*/
|
||||||
|
public class LocalStreamConnection {
|
||||||
|
private class LocalServerStreamConnection implements ServerStreamConnection {
|
||||||
|
public InputStream newInputStream() throws IOException {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputStream newOutputStream() throws IOException {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() throws IOException {
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final InputStream request;
|
||||||
|
private final XmlRpcStreamRequestConfig config;
|
||||||
|
private final ByteArrayOutputStream response = new ByteArrayOutputStream();
|
||||||
|
private final ServerStreamConnection serverStreamConnection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance with the given request stream.
|
||||||
|
* @param pConfig config
|
||||||
|
* @param pRequest request
|
||||||
|
*/
|
||||||
|
public LocalStreamConnection(XmlRpcStreamRequestConfig pConfig,
|
||||||
|
InputStream pRequest) {
|
||||||
|
config = pConfig;
|
||||||
|
request = pRequest;
|
||||||
|
serverStreamConnection = new LocalServerStreamConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the request stream.
|
||||||
|
* @return stream
|
||||||
|
*/
|
||||||
|
public InputStream getRequest() {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the request configuration.
|
||||||
|
* @return config
|
||||||
|
*/
|
||||||
|
public XmlRpcStreamRequestConfig getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an output stream, to which the response
|
||||||
|
* may be written.
|
||||||
|
* @return response
|
||||||
|
*/
|
||||||
|
public ByteArrayOutputStream getResponse() {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the server connection.
|
||||||
|
* @return server connection
|
||||||
|
*/
|
||||||
|
public ServerStreamConnection getServerStreamConnection() {
|
||||||
|
return serverStreamConnection;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface of an object, which is able to provide
|
||||||
|
* an XML stream, containing an XML-RPC request.
|
||||||
|
* Additionally, the object may also be used to
|
||||||
|
* write the response as an XML stream.
|
||||||
|
*/
|
||||||
|
public interface ServerStreamConnection {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the connection input stream.
|
||||||
|
* @return input stream
|
||||||
|
* @throws IOException if connection fails
|
||||||
|
*/
|
||||||
|
InputStream newInputStream() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the connection output stream.
|
||||||
|
* @return output stream
|
||||||
|
* @throws IOException if connection fails
|
||||||
|
*/
|
||||||
|
OutputStream newOutputStream() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the connection, and frees resources.
|
||||||
|
* @throws IOException if close fails
|
||||||
|
*/
|
||||||
|
void close() throws IOException;
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link TypeConverter} is used when actually calling the
|
||||||
|
* handler method or actually returning the result object. It's
|
||||||
|
* purpose is to convert a single parameter or the return value
|
||||||
|
* from a generic representation (for example an array of objects)
|
||||||
|
* to an alternative representation, which is actually used in
|
||||||
|
* the methods signature (for example {@link List}, or
|
||||||
|
* {@link Vector}.
|
||||||
|
*/
|
||||||
|
public interface TypeConverter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true whether the {@link TypeConverter} is
|
||||||
|
* ready to handle the given object. If so,
|
||||||
|
* {@link #convert(Object)} may be called.
|
||||||
|
* @param pObject object
|
||||||
|
* @return true
|
||||||
|
*/
|
||||||
|
boolean isConvertable(Object pObject);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given object into the required
|
||||||
|
* representation.
|
||||||
|
* @param pObject object
|
||||||
|
* @return object
|
||||||
|
*/
|
||||||
|
Object convert(Object pObject);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given object into its generic
|
||||||
|
* representation.
|
||||||
|
* @param result result
|
||||||
|
* @return object
|
||||||
|
*/
|
||||||
|
Object backConvert(Object result);
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link TypeConverterFactory} is called for creating instances
|
||||||
|
* of {@link TypeConverter}.
|
||||||
|
*/
|
||||||
|
public interface TypeConverterFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of {@link TypeFactory}, which may be
|
||||||
|
* used to create instances of the given class.
|
||||||
|
* @param pClass class
|
||||||
|
* @return type converter
|
||||||
|
*/
|
||||||
|
TypeConverter getTypeConverter(Class<?> pClass);
|
||||||
|
}
|
|
@ -0,0 +1,322 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation of {@link TypeConverterFactory}.
|
||||||
|
*/
|
||||||
|
public class TypeConverterFactoryImpl implements TypeConverterFactory {
|
||||||
|
|
||||||
|
private static class IdentityTypeConverter implements TypeConverter {
|
||||||
|
|
||||||
|
private final Class<?> clazz;
|
||||||
|
|
||||||
|
IdentityTypeConverter(Class<?> pClass) {
|
||||||
|
clazz = pClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConvertable(Object pObject) {
|
||||||
|
return pObject == null || clazz.isAssignableFrom(pObject.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object convert(Object pObject) {
|
||||||
|
return pObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object backConvert(Object pObject) {
|
||||||
|
return pObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static abstract class ListTypeConverter implements TypeConverter {
|
||||||
|
|
||||||
|
private final Class<?> clazz;
|
||||||
|
|
||||||
|
ListTypeConverter(Class<?> pClass) {
|
||||||
|
clazz = pClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract List<Object> newList(int pSize);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConvertable(Object pObject) {
|
||||||
|
return pObject == null || pObject instanceof Object[] || pObject instanceof Collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public Object convert(Object pObject) {
|
||||||
|
if (pObject == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (clazz.isAssignableFrom(pObject.getClass())) {
|
||||||
|
return pObject;
|
||||||
|
}
|
||||||
|
if (pObject instanceof Object[]) {
|
||||||
|
Object[] objects = (Object[]) pObject;
|
||||||
|
List<Object> result = newList(objects.length);
|
||||||
|
result.addAll(Arrays.asList(objects));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
Collection<Object> collection = (Collection) pObject;
|
||||||
|
List<Object> result = newList(collection.size());
|
||||||
|
result.addAll(collection);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object backConvert(Object pObject) {
|
||||||
|
return ((List) pObject).toArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PrimitiveTypeConverter implements TypeConverter {
|
||||||
|
|
||||||
|
private final Class<?> clazz;
|
||||||
|
|
||||||
|
PrimitiveTypeConverter(Class<?> pClass) {
|
||||||
|
clazz = pClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConvertable(Object pObject) {
|
||||||
|
return pObject != null && pObject.getClass().isAssignableFrom(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object convert(Object pObject) {
|
||||||
|
return pObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object backConvert(Object pObject) {
|
||||||
|
return pObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final TypeConverter voidTypeConverter = new IdentityTypeConverter(void.class);
|
||||||
|
private static final TypeConverter mapTypeConverter = new IdentityTypeConverter(Map.class);
|
||||||
|
private static final TypeConverter objectArrayTypeConverter = new IdentityTypeConverter(Object[].class);
|
||||||
|
private static final TypeConverter byteArrayTypeConverter = new IdentityTypeConverter(byte[].class);
|
||||||
|
private static final TypeConverter stringTypeConverter = new IdentityTypeConverter(String.class);
|
||||||
|
private static final TypeConverter booleanTypeConverter = new IdentityTypeConverter(Boolean.class);
|
||||||
|
private static final TypeConverter characterTypeConverter = new IdentityTypeConverter(Character.class);
|
||||||
|
private static final TypeConverter byteTypeConverter = new IdentityTypeConverter(Byte.class);
|
||||||
|
private static final TypeConverter shortTypeConverter = new IdentityTypeConverter(Short.class);
|
||||||
|
private static final TypeConverter integerTypeConverter = new IdentityTypeConverter(Integer.class);
|
||||||
|
private static final TypeConverter longTypeConverter = new IdentityTypeConverter(Long.class);
|
||||||
|
private static final TypeConverter bigDecimalTypeConverter = new IdentityTypeConverter(BigDecimal.class);
|
||||||
|
private static final TypeConverter bigIntegerTypeConverter = new IdentityTypeConverter(BigInteger.class);
|
||||||
|
private static final TypeConverter floatTypeConverter = new IdentityTypeConverter(Float.class);
|
||||||
|
private static final TypeConverter doubleTypeConverter = new IdentityTypeConverter(Double.class);
|
||||||
|
private static final TypeConverter dateTypeConverter = new IdentityTypeConverter(Date.class);
|
||||||
|
private static final TypeConverter calendarTypeConverter = new IdentityTypeConverter(Calendar.class);
|
||||||
|
private static final TypeConverter domTypeConverter = new IdentityTypeConverter(Document.class);
|
||||||
|
private static final TypeConverter primitiveBooleanTypeConverter = new PrimitiveTypeConverter(Boolean.class);
|
||||||
|
private static final TypeConverter primitiveCharTypeConverter = new PrimitiveTypeConverter(Character.class);
|
||||||
|
private static final TypeConverter primitiveByteTypeConverter = new PrimitiveTypeConverter(Byte.class);
|
||||||
|
private static final TypeConverter primitiveShortTypeConverter = new PrimitiveTypeConverter(Short.class);
|
||||||
|
private static final TypeConverter primitiveIntTypeConverter = new PrimitiveTypeConverter(Integer.class);
|
||||||
|
private static final TypeConverter primitiveLongTypeConverter = new PrimitiveTypeConverter(Long.class);
|
||||||
|
private static final TypeConverter primitiveFloatTypeConverter = new PrimitiveTypeConverter(Float.class);
|
||||||
|
private static final TypeConverter primitiveDoubleTypeConverter = new PrimitiveTypeConverter(Double.class);
|
||||||
|
|
||||||
|
private static final TypeConverter propertiesTypeConverter = new TypeConverter() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConvertable(Object pObject) {
|
||||||
|
return pObject == null || pObject instanceof Map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object convert(Object pObject) {
|
||||||
|
if (pObject == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Properties props = new Properties();
|
||||||
|
props.putAll((Map) pObject);
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object backConvert(Object pObject) {
|
||||||
|
return pObject;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final TypeConverter hashTableTypeConverter = new TypeConverter() {
|
||||||
|
@Override
|
||||||
|
public boolean isConvertable(Object pObject) {
|
||||||
|
return pObject == null || pObject instanceof Map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public Object convert(Object pObject) {
|
||||||
|
if (pObject == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new Hashtable<>((Map) pObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object backConvert(Object pObject) {
|
||||||
|
return pObject;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final TypeConverter listTypeConverter = new ListTypeConverter(List.class) {
|
||||||
|
@Override
|
||||||
|
protected List<Object> newList(int pSize) {
|
||||||
|
return new ArrayList<>(pSize);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final TypeConverter vectorTypeConverter = new ListTypeConverter(Vector.class) {
|
||||||
|
@Override
|
||||||
|
protected List<Object> newList(int pSize) {
|
||||||
|
return new Vector<>(pSize);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static class CastCheckingTypeConverter implements TypeConverter {
|
||||||
|
|
||||||
|
private final Class<?> clazz;
|
||||||
|
|
||||||
|
CastCheckingTypeConverter(Class<?> pClass) {
|
||||||
|
clazz = pClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConvertable(Object pObject) {
|
||||||
|
return pObject == null || clazz.isAssignableFrom(pObject.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object convert(Object pObject) {
|
||||||
|
return pObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object backConvert(Object pObject) {
|
||||||
|
return pObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a type converter for the given class.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public TypeConverter getTypeConverter(Class<?> pClass) {
|
||||||
|
if (void.class.equals(pClass)) {
|
||||||
|
return voidTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(boolean.class)) {
|
||||||
|
return primitiveBooleanTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(char.class)) {
|
||||||
|
return primitiveCharTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(byte.class)) {
|
||||||
|
return primitiveByteTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(short.class)) {
|
||||||
|
return primitiveShortTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(int.class)) {
|
||||||
|
return primitiveIntTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(long.class)) {
|
||||||
|
return primitiveLongTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(float.class)) {
|
||||||
|
return primitiveFloatTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(double.class)) {
|
||||||
|
return primitiveDoubleTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(String.class)) {
|
||||||
|
return stringTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(Boolean.class)) {
|
||||||
|
return booleanTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(Character.class)) {
|
||||||
|
return characterTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(Byte.class)) {
|
||||||
|
return byteTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(Short.class)) {
|
||||||
|
return shortTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(Integer.class)) {
|
||||||
|
return integerTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(Long.class)) {
|
||||||
|
return longTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(BigDecimal.class)) {
|
||||||
|
return bigDecimalTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(BigInteger.class)) {
|
||||||
|
return bigIntegerTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(Float.class)) {
|
||||||
|
return floatTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(Double.class)) {
|
||||||
|
return doubleTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(Date.class)) {
|
||||||
|
return dateTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(Calendar.class)) {
|
||||||
|
return calendarTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(Object[].class)) {
|
||||||
|
return objectArrayTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(List.class)) {
|
||||||
|
return listTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(Vector.class)) {
|
||||||
|
return vectorTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(Map.class)) {
|
||||||
|
return mapTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(Hashtable.class)) {
|
||||||
|
return hashTableTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(Properties.class)) {
|
||||||
|
return propertiesTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(byte[].class)) {
|
||||||
|
return byteArrayTypeConverter;
|
||||||
|
}
|
||||||
|
if (pClass.isAssignableFrom(Document.class)) {
|
||||||
|
return domTypeConverter;
|
||||||
|
}
|
||||||
|
if (Serializable.class.isAssignableFrom(pClass)) {
|
||||||
|
return new CastCheckingTypeConverter(pClass);
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("Invalid parameter or result type: " + pClass.getName());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.parser.TypeParser;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.serializer.TypeSerializer;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.util.NamespaceContextImpl;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type factory creates serializers or handlers, based on the object
|
||||||
|
* type.
|
||||||
|
*/
|
||||||
|
public interface TypeFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a serializer for the object <code>pObject</code>.
|
||||||
|
* @param pConfig The request configuration.
|
||||||
|
* @param pObject The object being serialized.
|
||||||
|
* @return A serializer for <code>pObject</code>.
|
||||||
|
* @throws SAXException Creating the serializer failed.
|
||||||
|
*/
|
||||||
|
TypeSerializer getSerializer(XmlRpcStreamConfig pConfig, Object pObject) throws SAXException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a parser for a parameter or result object.
|
||||||
|
* @param pConfig The request configuration.
|
||||||
|
* @param pContext A namespace context, for looking up prefix mappings.
|
||||||
|
* @param pURI The namespace URI of the element containing the parameter or result.
|
||||||
|
* @param pLocalName The local name of the element containing the parameter or result.
|
||||||
|
* @return The created parser.
|
||||||
|
*/
|
||||||
|
TypeParser getParser(XmlRpcStreamConfig pConfig, NamespaceContextImpl pContext, String pURI, String pLocalName);
|
||||||
|
}
|
|
@ -0,0 +1,220 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.parser.BigDecimalParser;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.parser.BigIntegerParser;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.parser.BooleanParser;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.parser.ByteArrayParser;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.parser.CalendarParser;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.parser.DateParser;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.parser.DoubleParser;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.parser.FloatParser;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.parser.I1Parser;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.parser.I2Parser;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.parser.I4Parser;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.parser.I8Parser;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.parser.MapParser;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.parser.NullParser;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.parser.ObjectArrayParser;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.parser.StringParser;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.parser.TypeParser;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.serializer.BigDecimalSerializer;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.serializer.BigIntegerSerializer;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.serializer.BooleanSerializer;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.serializer.ByteArraySerializer;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.serializer.CalendarSerializer;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.serializer.DateSerializer;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.serializer.DoubleSerializer;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.serializer.FloatSerializer;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.serializer.I1Serializer;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.serializer.I2Serializer;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.serializer.I4Serializer;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.serializer.I8Serializer;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.serializer.ListSerializer;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.serializer.MapSerializer;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.serializer.NullSerializer;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.serializer.ObjectArraySerializer;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.serializer.StringSerializer;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.serializer.TypeSerializer;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.serializer.XmlRpcWriter;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.util.NamespaceContextImpl;
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.util.XmlRpcDateTimeDateFormat;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation of a type factory.
|
||||||
|
*/
|
||||||
|
public class TypeFactoryImpl implements TypeFactory {
|
||||||
|
private static final TypeSerializer NULL_SERIALIZER = new NullSerializer();
|
||||||
|
private static final TypeSerializer STRING_SERIALIZER = new StringSerializer();
|
||||||
|
private static final TypeSerializer I4_SERIALIZER = new I4Serializer();
|
||||||
|
private static final TypeSerializer BOOLEAN_SERIALIZER = new BooleanSerializer();
|
||||||
|
private static final TypeSerializer DOUBLE_SERIALIZER = new DoubleSerializer();
|
||||||
|
private static final TypeSerializer BYTE_SERIALIZER = new I1Serializer();
|
||||||
|
private static final TypeSerializer SHORT_SERIALIZER = new I2Serializer();
|
||||||
|
private static final TypeSerializer LONG_SERIALIZER = new I8Serializer();
|
||||||
|
private static final TypeSerializer FLOAT_SERIALIZER = new FloatSerializer();
|
||||||
|
private static final TypeSerializer BIGDECIMAL_SERIALIZER = new BigDecimalSerializer();
|
||||||
|
private static final TypeSerializer BIGINTEGER_SERIALIZER = new BigIntegerSerializer();
|
||||||
|
private static final TypeSerializer CALENDAR_SERIALIZER = new CalendarSerializer();
|
||||||
|
|
||||||
|
private final XmlRpcController controller;
|
||||||
|
private DateSerializer dateSerializer;
|
||||||
|
|
||||||
|
/** Creates a new instance.
|
||||||
|
* @param pController The controller, which operates the type factory.
|
||||||
|
*/
|
||||||
|
public TypeFactoryImpl(XmlRpcController pController) {
|
||||||
|
controller = pController;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the controller, which operates the type factory.
|
||||||
|
* @return The controller
|
||||||
|
*/
|
||||||
|
public XmlRpcController getController() {
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TypeSerializer getSerializer(XmlRpcStreamConfig pConfig, Object pObject) throws SAXException {
|
||||||
|
if (pObject == null) {
|
||||||
|
if (pConfig.isEnabledForExtensions()) {
|
||||||
|
return NULL_SERIALIZER;
|
||||||
|
} else {
|
||||||
|
throw new SAXException(new XmlRpcExtensionException("Null values aren't supported, if isEnabledForExtensions() == false"));
|
||||||
|
}
|
||||||
|
} else if (pObject instanceof String) {
|
||||||
|
return STRING_SERIALIZER;
|
||||||
|
} else if (pObject instanceof Byte) {
|
||||||
|
if (pConfig.isEnabledForExtensions()) {
|
||||||
|
return BYTE_SERIALIZER;
|
||||||
|
} else {
|
||||||
|
throw new SAXException(new XmlRpcExtensionException("Byte values aren't supported, if isEnabledForExtensions() == false"));
|
||||||
|
}
|
||||||
|
} else if (pObject instanceof Short) {
|
||||||
|
if (pConfig.isEnabledForExtensions()) {
|
||||||
|
return SHORT_SERIALIZER;
|
||||||
|
} else {
|
||||||
|
throw new SAXException(new XmlRpcExtensionException("Short values aren't supported, if isEnabledForExtensions() == false"));
|
||||||
|
}
|
||||||
|
} else if (pObject instanceof Integer) {
|
||||||
|
return I4_SERIALIZER;
|
||||||
|
} else if (pObject instanceof Long) {
|
||||||
|
if (pConfig.isEnabledForExtensions()) {
|
||||||
|
return LONG_SERIALIZER;
|
||||||
|
} else {
|
||||||
|
throw new SAXException(new XmlRpcExtensionException("Long values aren't supported, if isEnabledForExtensions() == false"));
|
||||||
|
}
|
||||||
|
} else if (pObject instanceof Boolean) {
|
||||||
|
return BOOLEAN_SERIALIZER;
|
||||||
|
} else if (pObject instanceof Float) {
|
||||||
|
if (pConfig.isEnabledForExtensions()) {
|
||||||
|
return FLOAT_SERIALIZER;
|
||||||
|
} else {
|
||||||
|
throw new SAXException(new XmlRpcExtensionException("Float values aren't supported, if isEnabledForExtensions() == false"));
|
||||||
|
}
|
||||||
|
} else if (pObject instanceof Double) {
|
||||||
|
return DOUBLE_SERIALIZER;
|
||||||
|
} else if (pObject instanceof Calendar) {
|
||||||
|
if (pConfig.isEnabledForExtensions()) {
|
||||||
|
return CALENDAR_SERIALIZER;
|
||||||
|
} else {
|
||||||
|
throw new SAXException(new XmlRpcExtensionException("Calendar values aren't supported, if isEnabledForExtensions() == false"));
|
||||||
|
}
|
||||||
|
} else if (pObject instanceof Date) {
|
||||||
|
if (dateSerializer == null) {
|
||||||
|
dateSerializer = new DateSerializer(new XmlRpcDateTimeDateFormat(){
|
||||||
|
private static final long serialVersionUID = 24345909123324234L;
|
||||||
|
protected TimeZone getTimeZone() {
|
||||||
|
return controller.getConfig().getTimeZone();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return dateSerializer;
|
||||||
|
} else if (pObject instanceof byte[]) {
|
||||||
|
return new ByteArraySerializer();
|
||||||
|
} else if (pObject instanceof Object[]) {
|
||||||
|
return new ObjectArraySerializer(this, pConfig);
|
||||||
|
} else if (pObject instanceof List) {
|
||||||
|
return new ListSerializer(this, pConfig);
|
||||||
|
} else if (pObject instanceof Map) {
|
||||||
|
return new MapSerializer(this, pConfig);
|
||||||
|
} else if (pObject instanceof Node) {
|
||||||
|
throw new SAXException(new XmlRpcExtensionException("DOM nodes aren't supported"));
|
||||||
|
} else if (pObject instanceof BigInteger) {
|
||||||
|
if (pConfig.isEnabledForExtensions()) {
|
||||||
|
return BIGINTEGER_SERIALIZER;
|
||||||
|
} else {
|
||||||
|
throw new SAXException(new XmlRpcExtensionException("BigInteger values aren't supported, if isEnabledForExtensions() == false"));
|
||||||
|
}
|
||||||
|
} else if (pObject instanceof BigDecimal) {
|
||||||
|
if (pConfig.isEnabledForExtensions()) {
|
||||||
|
return BIGDECIMAL_SERIALIZER;
|
||||||
|
} else {
|
||||||
|
throw new SAXException(new XmlRpcExtensionException("BigDecimal values aren't supported, if isEnabledForExtensions() == false"));
|
||||||
|
}
|
||||||
|
} else if (pObject instanceof Serializable) {
|
||||||
|
throw new SAXException(new XmlRpcExtensionException("Serializable objects aren't supported"));
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TypeParser getParser(XmlRpcStreamConfig pConfig, NamespaceContextImpl pContext, String pURI, String pLocalName) {
|
||||||
|
if (XmlRpcWriter.EXTENSIONS_URI.equals(pURI)) {
|
||||||
|
if (!pConfig.isEnabledForExtensions()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (NullSerializer.NIL_TAG.equals(pLocalName)) {
|
||||||
|
return new NullParser();
|
||||||
|
} else if (I1Serializer.I1_TAG.equals(pLocalName)) {
|
||||||
|
return new I1Parser();
|
||||||
|
} else if (I2Serializer.I2_TAG.equals(pLocalName)) {
|
||||||
|
return new I2Parser();
|
||||||
|
} else if (I8Serializer.I8_TAG.equals(pLocalName)) {
|
||||||
|
return new I8Parser();
|
||||||
|
} else if (FloatSerializer.FLOAT_TAG.equals(pLocalName)) {
|
||||||
|
return new FloatParser();
|
||||||
|
} else if (BigDecimalSerializer.BIGDECIMAL_TAG.equals(pLocalName)) {
|
||||||
|
return new BigDecimalParser();
|
||||||
|
} else if (BigIntegerSerializer.BIGINTEGER_TAG.equals(pLocalName)) {
|
||||||
|
return new BigIntegerParser();
|
||||||
|
} else if (CalendarSerializer.CALENDAR_TAG.equals(pLocalName)) {
|
||||||
|
return new CalendarParser();
|
||||||
|
}
|
||||||
|
} else if ("".equals(pURI)) {
|
||||||
|
if (I4Serializer.INT_TAG.equals(pLocalName) || I4Serializer.I4_TAG.equals(pLocalName)) {
|
||||||
|
return new I4Parser();
|
||||||
|
} else if (BooleanSerializer.BOOLEAN_TAG.equals(pLocalName)) {
|
||||||
|
return new BooleanParser();
|
||||||
|
} else if (DoubleSerializer.DOUBLE_TAG.equals(pLocalName)) {
|
||||||
|
return new DoubleParser();
|
||||||
|
} else if (DateSerializer.DATE_TAG.equals(pLocalName)) {
|
||||||
|
return new DateParser(new XmlRpcDateTimeDateFormat(){
|
||||||
|
private static final long serialVersionUID = 7585237706442299067L;
|
||||||
|
protected TimeZone getTimeZone() {
|
||||||
|
return controller.getConfig().getTimeZone();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (ObjectArraySerializer.ARRAY_TAG.equals(pLocalName)) {
|
||||||
|
return new ObjectArrayParser(pConfig, pContext, this);
|
||||||
|
} else if (MapSerializer.STRUCT_TAG.equals(pLocalName)) {
|
||||||
|
return new MapParser(pConfig, pContext, this);
|
||||||
|
} else if (ByteArraySerializer.BASE_64_TAG.equals(pLocalName)) {
|
||||||
|
return new ByteArrayParser();
|
||||||
|
} else if (StringSerializer.STRING_TAG.equals(pLocalName)) {
|
||||||
|
return new StringParser();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
public interface XmlRpcConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns, whether support for extensions are enabled.
|
||||||
|
* By default, extensions are disabled and your client is
|
||||||
|
* interoperable with other XML-RPC implementations.
|
||||||
|
* Interoperable XML-RPC implementations are those, which
|
||||||
|
* are compliant to the
|
||||||
|
* <a href="http://www.xmlrpc.org/spec">XML-RPC Specification</a>.
|
||||||
|
* @return Whether extensions are enabled or not.
|
||||||
|
*/
|
||||||
|
boolean isEnabledForExtensions();
|
||||||
|
|
||||||
|
/** Returns the timezone, which is used to interpret date/time
|
||||||
|
* values. Defaults to {@link TimeZone#getDefault()}.
|
||||||
|
* @return time zone
|
||||||
|
*/
|
||||||
|
TimeZone getTimeZone();
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation of {@link XmlRpcConfig}.
|
||||||
|
*/
|
||||||
|
public abstract class XmlRpcConfigImpl implements XmlRpcConfig, XmlRpcHttpConfig {
|
||||||
|
|
||||||
|
private boolean enabledForExtensions;
|
||||||
|
|
||||||
|
private boolean contentLengthOptional;
|
||||||
|
|
||||||
|
private String basicEncoding;
|
||||||
|
|
||||||
|
private String encoding;
|
||||||
|
|
||||||
|
private TimeZone timeZone = TimeZone.getDefault();
|
||||||
|
|
||||||
|
public boolean isEnabledForExtensions() { return enabledForExtensions; }
|
||||||
|
|
||||||
|
/** Sets, whether extensions are enabled. By default, the
|
||||||
|
* client or server is strictly compliant to the XML-RPC
|
||||||
|
* specification and extensions are disabled.
|
||||||
|
* @param pExtensions True to enable extensions, false otherwise.
|
||||||
|
*/
|
||||||
|
public void setEnabledForExtensions(boolean pExtensions) {
|
||||||
|
enabledForExtensions = pExtensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the encoding for basic authentication.
|
||||||
|
* @param pEncoding The encoding; may be null, in which case
|
||||||
|
* UTF-8 is choosen.
|
||||||
|
*/
|
||||||
|
public void setBasicEncoding(String pEncoding) {
|
||||||
|
basicEncoding = pEncoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBasicEncoding() { return basicEncoding; }
|
||||||
|
|
||||||
|
/** Sets the requests encoding.
|
||||||
|
* @param pEncoding The requests encoding or null (default
|
||||||
|
* UTF-8).
|
||||||
|
*/
|
||||||
|
public void setEncoding(String pEncoding) {
|
||||||
|
encoding = pEncoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEncoding() { return encoding; }
|
||||||
|
|
||||||
|
public boolean isContentLengthOptional() {
|
||||||
|
return contentLengthOptional;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets, whether a "Content-Length" header may be
|
||||||
|
* omitted. The XML-RPC specification demands, that such
|
||||||
|
* a header be present.
|
||||||
|
* @param pContentLengthOptional True, if the content length may be omitted.
|
||||||
|
*/
|
||||||
|
public void setContentLengthOptional(boolean pContentLengthOptional) {
|
||||||
|
contentLengthOptional = pContentLengthOptional;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeZone getTimeZone() {
|
||||||
|
return timeZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the timezone, which is used to interpret date/time
|
||||||
|
* values. Defaults to {@link TimeZone#getDefault()}.
|
||||||
|
* @param pTimeZone time zone
|
||||||
|
*/
|
||||||
|
public void setTimeZone(TimeZone pTimeZone) {
|
||||||
|
timeZone = pTimeZone;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class XmlRpcController {
|
||||||
|
|
||||||
|
private XmlRpcWorkerFactory workerFactory = getDefaultXmlRpcWorkerFactory();
|
||||||
|
|
||||||
|
private int maxThreads;
|
||||||
|
|
||||||
|
private TypeFactory typeFactory = new TypeFactoryImpl(this);
|
||||||
|
|
||||||
|
/** Creates the controllers default worker factory.
|
||||||
|
* @return The default factory for workers.
|
||||||
|
*/
|
||||||
|
protected abstract XmlRpcWorkerFactory getDefaultXmlRpcWorkerFactory();
|
||||||
|
|
||||||
|
/** Sets the maximum number of concurrent requests. This includes
|
||||||
|
* both synchronous and asynchronous requests.
|
||||||
|
* @param pMaxThreads Maximum number of threads or 0 to disable
|
||||||
|
* the limit.
|
||||||
|
*/
|
||||||
|
public void setMaxThreads(int pMaxThreads) {
|
||||||
|
maxThreads = pMaxThreads;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the maximum number of concurrent requests. This includes
|
||||||
|
* both synchronous and asynchronous requests.
|
||||||
|
* @return Maximum number of threads or 0 to disable
|
||||||
|
* the limit.
|
||||||
|
*/
|
||||||
|
public int getMaxThreads() {
|
||||||
|
return maxThreads;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the clients worker factory.
|
||||||
|
* @param pFactory The factory being used to create workers.
|
||||||
|
*/
|
||||||
|
public void setWorkerFactory(XmlRpcWorkerFactory pFactory) {
|
||||||
|
workerFactory = pFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the clients worker factory.
|
||||||
|
* @return The factory being used to create workers.
|
||||||
|
*/
|
||||||
|
public XmlRpcWorkerFactory getWorkerFactory() {
|
||||||
|
return workerFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the controllers default configuration.
|
||||||
|
* @return The default configuration.
|
||||||
|
*/
|
||||||
|
public abstract XmlRpcConfig getConfig();
|
||||||
|
|
||||||
|
/** Sets the type factory.
|
||||||
|
* @param pTypeFactory The type factory.
|
||||||
|
*/
|
||||||
|
public void setTypeFactory(TypeFactory pTypeFactory) {
|
||||||
|
typeFactory = pTypeFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the type factory.
|
||||||
|
* @return The type factory.
|
||||||
|
*/
|
||||||
|
public TypeFactory getTypeFactory() {
|
||||||
|
return typeFactory;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exception is thrown by the XmlRpcClient, if an invocation of the
|
||||||
|
* remote method failed. Failure may have two reasons: The invocation
|
||||||
|
* failed on the remote side (for example, an exception was thrown within
|
||||||
|
* the server) or the communication with the server failed.
|
||||||
|
*/
|
||||||
|
public class XmlRpcException extends Exception {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 3258693217049325618L;
|
||||||
|
|
||||||
|
/** The fault code of the exception. For servers based on this library, this
|
||||||
|
* will always be 0. (If there are predefined error codes, they should be in
|
||||||
|
* the XML-RPC spec.)
|
||||||
|
*/
|
||||||
|
public final int code;
|
||||||
|
|
||||||
|
/** If the transport was able to catch a remote exception
|
||||||
|
* (as is the case, if the local transport is used or if extensions
|
||||||
|
* are enabled and the server returned a serialized exception),
|
||||||
|
* then this field contains the trapped exception.
|
||||||
|
*/
|
||||||
|
public final Throwable linkedException;
|
||||||
|
|
||||||
|
/** Creates a new instance with the given error code and error message.
|
||||||
|
* @param pCode Error code.
|
||||||
|
* @param pMessage Detail message.
|
||||||
|
*/
|
||||||
|
public XmlRpcException(int pCode, String pMessage) {
|
||||||
|
this(pCode, pMessage, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a new instance with the given error message
|
||||||
|
* and cause.
|
||||||
|
* @param pMessage Detail message.
|
||||||
|
* @param pLinkedException The errors cause.
|
||||||
|
*/
|
||||||
|
public XmlRpcException(String pMessage, Throwable pLinkedException) {
|
||||||
|
this(0, pMessage, pLinkedException);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a new instance with the given error message
|
||||||
|
* and error code 0.
|
||||||
|
* @param pMessage Detail message.
|
||||||
|
*/
|
||||||
|
public XmlRpcException(String pMessage) {
|
||||||
|
this(0, pMessage, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a new instance with the given error code, error message
|
||||||
|
* and cause.
|
||||||
|
* @param pCode Error code.
|
||||||
|
* @param pMessage Detail message.
|
||||||
|
* @param pLinkedException The errors cause.
|
||||||
|
*/
|
||||||
|
public XmlRpcException(int pCode, String pMessage, Throwable pLinkedException) {
|
||||||
|
super(pMessage);
|
||||||
|
code = pCode;
|
||||||
|
linkedException = pLinkedException;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void printStackTrace(PrintStream pStream) {
|
||||||
|
super.printStackTrace(pStream);
|
||||||
|
if (linkedException != null) {
|
||||||
|
pStream.println("Caused by:");
|
||||||
|
linkedException.printStackTrace(pStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void printStackTrace(PrintWriter pWriter) {
|
||||||
|
super.printStackTrace(pWriter);
|
||||||
|
if (linkedException != null) {
|
||||||
|
pWriter.println("Caused by:");
|
||||||
|
linkedException.printStackTrace(pWriter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Throwable getCause() {
|
||||||
|
return linkedException;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exception is thrown, if an attempt to use extensions
|
||||||
|
* is made, but extensions aren't explicitly enabled.
|
||||||
|
*/
|
||||||
|
public class XmlRpcExtensionException extends XmlRpcException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 3617014169594311221L;
|
||||||
|
|
||||||
|
/** Creates a new instance with the given error message.
|
||||||
|
* @param pMessage The error message.
|
||||||
|
*/
|
||||||
|
public XmlRpcExtensionException(String pMessage) {
|
||||||
|
super(0, pMessage);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The XML-RPC server uses this interface to call a method of an RPC handler.
|
||||||
|
*/
|
||||||
|
public interface XmlRpcHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the request and returns the result object.
|
||||||
|
* @param pRequest The request being performed (method name and
|
||||||
|
* parameters.)
|
||||||
|
* @return The result object.
|
||||||
|
* @throws XmlRpcException Performing the request failed.
|
||||||
|
*/
|
||||||
|
Object execute(XmlRpcRequest pRequest) throws XmlRpcException;
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface of a configuration for HTTP requests.
|
||||||
|
*/
|
||||||
|
public interface XmlRpcHttpConfig extends XmlRpcStreamConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the encoding being used to convert the String "username:password"
|
||||||
|
* into bytes.
|
||||||
|
* @return Encoding being used for basic HTTP authentication credentials,
|
||||||
|
* or null, if the default encoding
|
||||||
|
* ({@link XmlRpcStreamRequestConfig#UTF8_ENCODING})
|
||||||
|
* is being used.
|
||||||
|
*/
|
||||||
|
String getBasicEncoding();
|
||||||
|
|
||||||
|
/** Returns, whether a "Content-Length" header may be
|
||||||
|
* omitted. The XML-RPC specification demands, that such
|
||||||
|
* a header be present.
|
||||||
|
* @return True, if the content length may be omitted.
|
||||||
|
*/
|
||||||
|
boolean isContentLengthOptional();
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension for HTTP based transport. Provides details like server URL,
|
||||||
|
* user credentials, and so on.
|
||||||
|
*/
|
||||||
|
public interface XmlRpcHttpRequestConfig extends XmlRpcStreamRequestConfig, XmlRpcHttpConfig {
|
||||||
|
|
||||||
|
/** Returns the user name being used for basic HTTP authentication.
|
||||||
|
* @return User name or null, if no basic HTTP authentication is being used.
|
||||||
|
*/
|
||||||
|
String getBasicUserName();
|
||||||
|
|
||||||
|
/** Returns the password being used for basic HTTP authentication.
|
||||||
|
* @return Password or null, if no basic HTTP authentication is beind used.
|
||||||
|
* @throws IllegalStateException A user name is configured, but no password.
|
||||||
|
*/
|
||||||
|
String getBasicPassword();
|
||||||
|
|
||||||
|
/** Return the connection timeout in milliseconds
|
||||||
|
* @return connection timeout in milliseconds or 0 if no set
|
||||||
|
*/
|
||||||
|
int getConnectionTimeout();
|
||||||
|
|
||||||
|
/** Return the reply timeout in milliseconds
|
||||||
|
* @return reply timeout in milliseconds or 0 if no set
|
||||||
|
*/
|
||||||
|
int getReplyTimeout();
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation of a request configuration.
|
||||||
|
*/
|
||||||
|
public class XmlRpcHttpRequestConfigImpl extends XmlRpcConfigImpl implements
|
||||||
|
XmlRpcHttpRequestConfig {
|
||||||
|
|
||||||
|
private boolean gzipCompressing;
|
||||||
|
|
||||||
|
private boolean gzipRequesting;
|
||||||
|
|
||||||
|
private String basicUserName;
|
||||||
|
|
||||||
|
private String basicPassword;
|
||||||
|
|
||||||
|
private int connectionTimeout = 0;
|
||||||
|
|
||||||
|
private int replyTimeout = 0;
|
||||||
|
|
||||||
|
private boolean enabledForExceptions;
|
||||||
|
|
||||||
|
/** Sets, whether gzip compression is being used for
|
||||||
|
* transmitting the request.
|
||||||
|
* @param pCompressing True for enabling gzip compression,
|
||||||
|
* false otherwise.
|
||||||
|
* @see #setGzipRequesting(boolean)
|
||||||
|
*/
|
||||||
|
public void setGzipCompressing(boolean pCompressing) {
|
||||||
|
gzipCompressing = pCompressing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGzipCompressing() {
|
||||||
|
return gzipCompressing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets, whether gzip compression is requested for the
|
||||||
|
* response.
|
||||||
|
* @param pRequesting True for requesting gzip compression,
|
||||||
|
* false otherwise.
|
||||||
|
* @see #setGzipCompressing(boolean)
|
||||||
|
*/
|
||||||
|
public void setGzipRequesting(boolean pRequesting) {
|
||||||
|
gzipRequesting = pRequesting;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGzipRequesting() {
|
||||||
|
return gzipRequesting;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the user name for basic authentication.
|
||||||
|
* @param pUser The user name.
|
||||||
|
*/
|
||||||
|
public void setBasicUserName(String pUser) {
|
||||||
|
basicUserName = pUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBasicUserName() { return basicUserName; }
|
||||||
|
|
||||||
|
/** Sets the password for basic authentication.
|
||||||
|
* @param pPassword The password.
|
||||||
|
*/
|
||||||
|
public void setBasicPassword(String pPassword) {
|
||||||
|
basicPassword = pPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBasicPassword() { return basicPassword; }
|
||||||
|
|
||||||
|
/** Set the connection timeout in milliseconds.
|
||||||
|
* @param pTimeout connection timeout, 0 to disable it
|
||||||
|
*/
|
||||||
|
public void setConnectionTimeout(int pTimeout) {
|
||||||
|
connectionTimeout = pTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getConnectionTimeout() {
|
||||||
|
return connectionTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the reply timeout in milliseconds.
|
||||||
|
* @param pTimeout reply timeout, 0 to disable it
|
||||||
|
*/
|
||||||
|
public void setReplyTimeout(int pTimeout) {
|
||||||
|
replyTimeout = pTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getReplyTimeout() {
|
||||||
|
return replyTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets, whether the response should contain a "faultCause" element
|
||||||
|
* in case of errors. The "faultCause" is an exception, which the
|
||||||
|
* server has trapped and written into a byte stream as a serializable
|
||||||
|
* object.
|
||||||
|
* @param pEnabledForExceptions enabled for exceptions
|
||||||
|
*/
|
||||||
|
public void setEnabledForExceptions(boolean pEnabledForExceptions) {
|
||||||
|
enabledForExceptions = pEnabledForExceptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabledForExceptions() {
|
||||||
|
return enabledForExceptions;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exception is thrown, if the server catches an exception, which
|
||||||
|
* is thrown by the handler.
|
||||||
|
*/
|
||||||
|
public class XmlRpcInvocationException extends XmlRpcException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 7439737967784966169L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance with the given error code, error message
|
||||||
|
* and cause.
|
||||||
|
* @param pCode code
|
||||||
|
* @param pMessage message
|
||||||
|
* @param pLinkedException exception
|
||||||
|
*/
|
||||||
|
public XmlRpcInvocationException(int pCode, String pMessage, Throwable pLinkedException) {
|
||||||
|
super(pCode, pMessage, pLinkedException);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance with the given error message and cause.
|
||||||
|
* @param pMessage message
|
||||||
|
* @param pLinkedException exception
|
||||||
|
*/
|
||||||
|
public XmlRpcInvocationException(String pMessage, Throwable pLinkedException) {
|
||||||
|
super(pMessage, pLinkedException);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exception is thrown, if the clients or servers maximum
|
||||||
|
* number of concurrent threads is exceeded.
|
||||||
|
*/
|
||||||
|
public class XmlRpcLoadException extends XmlRpcException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 4050760511635272755L;
|
||||||
|
|
||||||
|
/** Creates a new instance.
|
||||||
|
* @param pMessage Error description.
|
||||||
|
*/
|
||||||
|
public XmlRpcLoadException(String pMessage) {
|
||||||
|
super(0, pMessage, null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exception must be thrown, if the user isn't authenticated.
|
||||||
|
*/
|
||||||
|
public class XmlRpcNotAuthorizedException extends XmlRpcException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 3258410629709574201L;
|
||||||
|
|
||||||
|
/** Creates a new instance with the given error message.
|
||||||
|
* @param pMessage The error message.
|
||||||
|
*/
|
||||||
|
public XmlRpcNotAuthorizedException(String pMessage) {
|
||||||
|
super(0, pMessage);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to an XML-RPC request made by a client.
|
||||||
|
*/
|
||||||
|
public interface XmlRpcRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the request configuration.
|
||||||
|
* @return The request configuration.
|
||||||
|
*/
|
||||||
|
XmlRpcRequestConfig getConfig();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the requests method name.
|
||||||
|
* @return Name of the method being invoked.
|
||||||
|
*/
|
||||||
|
String getMethodName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of parameters.
|
||||||
|
* @return Number of parameters.
|
||||||
|
*/
|
||||||
|
int getParameterCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the parameter with index <code>pIndex</code>.
|
||||||
|
* @param pIndex Number between 0 and {@link #getParameterCount()}-1.
|
||||||
|
* @return Parameter being sent to the server.
|
||||||
|
*/
|
||||||
|
public Object getParameter(int pIndex);
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface of a request configuration. Depending on
|
||||||
|
* the transport, implementations will also implement
|
||||||
|
* additional interfaces like
|
||||||
|
* {@link XmlRpcStreamRequestConfig}.
|
||||||
|
*/
|
||||||
|
public interface XmlRpcRequestConfig extends XmlRpcConfig {
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface of an object, which is able to process
|
||||||
|
* XML-RPC requests.
|
||||||
|
*/
|
||||||
|
public interface XmlRpcRequestProcessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the given request and returns a
|
||||||
|
* result object.
|
||||||
|
* @param pRequest request
|
||||||
|
* @return result
|
||||||
|
* @throws XmlRpcException Processing the request failed.
|
||||||
|
*/
|
||||||
|
Object execute(XmlRpcRequest pRequest) throws XmlRpcException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the request processors {@link TypeConverterFactory}.
|
||||||
|
* @return type converter factory
|
||||||
|
*/
|
||||||
|
TypeConverterFactory getTypeConverterFactory();
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface of an object, which may be used
|
||||||
|
* to create instances of {@link XmlRpcRequestProcessor}.
|
||||||
|
*/
|
||||||
|
public interface XmlRpcRequestProcessorFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link XmlRpcRequestProcessor} being invoked.
|
||||||
|
* @return Server object being invoked. This will typically
|
||||||
|
* be a singleton instance, but could as well create a new
|
||||||
|
* instance with any call.
|
||||||
|
*/
|
||||||
|
XmlRpcRequestProcessor getXmlRpcServer();
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface of a configuration for a stream based transport.
|
||||||
|
*/
|
||||||
|
public interface XmlRpcStreamConfig extends XmlRpcConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default encoding (UTF-8).
|
||||||
|
*/
|
||||||
|
String UTF8_ENCODING = "UTF-8";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the encoding being used for data encoding, when writing
|
||||||
|
* to a stream.
|
||||||
|
* @return Suggested encoding, or null, if the {@link #UTF8_ENCODING}
|
||||||
|
* is being used.
|
||||||
|
*/
|
||||||
|
String getEncoding();
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface of a client configuration for a transport, which
|
||||||
|
* is implemented by writing to a stream.
|
||||||
|
*/
|
||||||
|
public interface XmlRpcStreamRequestConfig extends XmlRpcStreamConfig, XmlRpcRequestConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the request stream is being compressed. Note,
|
||||||
|
* that the response stream may still be uncompressed.
|
||||||
|
* @return Whether to use Gzip compression or not. Defaults to false.
|
||||||
|
* @see #isGzipRequesting()
|
||||||
|
*/
|
||||||
|
boolean isGzipCompressing();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if compression is requested for the response stream.
|
||||||
|
* Note, that the request is stull uncompressed, unless
|
||||||
|
* {@link #isGzipCompressing()} is activated. Also note, that the
|
||||||
|
* server may still decide to send uncompressed data.
|
||||||
|
* @return Whether to use Gzip compression or not. Defaults to false.
|
||||||
|
* @see #isGzipCompressing()
|
||||||
|
*/
|
||||||
|
boolean isGzipRequesting();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the response should contain a "faultCause" element
|
||||||
|
* in case of errors. The "faultCause" is an exception, which the
|
||||||
|
* server has trapped and written into a byte stream as a serializable
|
||||||
|
* object.
|
||||||
|
* @return true if enabled for exceptions
|
||||||
|
*/
|
||||||
|
boolean isEnabledForExceptions();
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An instance of {@link XmlRpcRequestProcessor},
|
||||||
|
* which is processing an XML stream.
|
||||||
|
*/
|
||||||
|
public interface XmlRpcStreamRequestProcessor extends XmlRpcRequestProcessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an XML-RPC request from the connection
|
||||||
|
* object and processes the request, writing the
|
||||||
|
* result to the same connection object.
|
||||||
|
* @param pConfig config
|
||||||
|
* @param pConnection connection
|
||||||
|
* @throws XmlRpcException Processing the request failed.
|
||||||
|
*/
|
||||||
|
void execute(XmlRpcStreamRequestConfig pConfig, ServerStreamConnection pConnection) throws XmlRpcException;
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object, which executes requests on the controllers
|
||||||
|
* behalf. These objects are mainly used for controlling the
|
||||||
|
* clients or servers load, which is defined in terms of the
|
||||||
|
* number of currently active workers.
|
||||||
|
*/
|
||||||
|
public interface XmlRpcWorker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the workers controller.
|
||||||
|
* @return The controller
|
||||||
|
*/
|
||||||
|
XmlRpcController getController();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a synchronous request. The client worker extends
|
||||||
|
* this interface with the ability to perform asynchronous
|
||||||
|
* requests.
|
||||||
|
* @param pRequest The request being performed.
|
||||||
|
* @return The requests result.
|
||||||
|
* @throws XmlRpcException Performing the request failed.
|
||||||
|
*/
|
||||||
|
Object execute(XmlRpcRequest pRequest) throws XmlRpcException;
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory for {@link XmlRpcWorker} instances.
|
||||||
|
*/
|
||||||
|
public abstract class XmlRpcWorkerFactory {
|
||||||
|
|
||||||
|
private final XmlRpcWorker singleton = newWorker();
|
||||||
|
|
||||||
|
private final XmlRpcController controller;
|
||||||
|
|
||||||
|
private final List<XmlRpcWorker> pool = new ArrayList<>();
|
||||||
|
|
||||||
|
private int numThreads;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance.
|
||||||
|
* @param pController The client controlling the factory.
|
||||||
|
*/
|
||||||
|
public XmlRpcWorkerFactory(XmlRpcController pController) {
|
||||||
|
controller = pController;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new worker instance.
|
||||||
|
* @return New instance of {@link XmlRpcWorker}.
|
||||||
|
*/
|
||||||
|
protected abstract XmlRpcWorker newWorker();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the factory controller.
|
||||||
|
* @return The controller
|
||||||
|
*/
|
||||||
|
public XmlRpcController getController() {
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a worker for synchronous processing.
|
||||||
|
* @return An instance of {@link XmlRpcWorker}, which is ready
|
||||||
|
* for use.
|
||||||
|
* @throws XmlRpcLoadException The clients maximum number of concurrent
|
||||||
|
* threads is exceeded.
|
||||||
|
*/
|
||||||
|
public synchronized XmlRpcWorker getWorker() throws XmlRpcLoadException {
|
||||||
|
int max = controller.getMaxThreads();
|
||||||
|
if (max > 0 && numThreads == max) {
|
||||||
|
throw new XmlRpcLoadException("Maximum number of concurrent requests exceeded: " + max);
|
||||||
|
}
|
||||||
|
if (max == 0) {
|
||||||
|
return singleton;
|
||||||
|
}
|
||||||
|
++numThreads;
|
||||||
|
if (pool.size() == 0) {
|
||||||
|
return newWorker();
|
||||||
|
} else {
|
||||||
|
return pool.remove(pool.size() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called, when the worker did its job. Frees resources and
|
||||||
|
* decrements the number of concurrent requests.
|
||||||
|
* @param pWorker The worker being released.
|
||||||
|
*/
|
||||||
|
public synchronized void releaseWorker(XmlRpcWorker pWorker) {
|
||||||
|
--numThreads;
|
||||||
|
int max = controller.getMaxThreads();
|
||||||
|
if (pWorker != singleton) {
|
||||||
|
if (pool.size() < max) {
|
||||||
|
pool.add(pWorker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of currently running requests.
|
||||||
|
* @return Current number of concurrent requests.
|
||||||
|
*/
|
||||||
|
public synchronized int getCurrentRequests() {
|
||||||
|
return numThreads;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common.parser;
|
||||||
|
|
||||||
|
import javax.xml.namespace.QName;
|
||||||
|
|
||||||
|
import org.xml.sax.Attributes;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
import org.xml.sax.SAXParseException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract base implementation of {@link TypeParser}
|
||||||
|
* for parsing an atomic value.
|
||||||
|
*/
|
||||||
|
public abstract class AtomicParser extends TypeParserImpl {
|
||||||
|
private int level;
|
||||||
|
protected StringBuffer sb;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance.
|
||||||
|
*/
|
||||||
|
protected AtomicParser() {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void setResult(String pResult) throws SAXException;
|
||||||
|
|
||||||
|
public void startDocument() throws SAXException {
|
||||||
|
level = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void characters(char[] pChars, int pStart, int pLength) throws SAXException {
|
||||||
|
if (sb == null) {
|
||||||
|
if (!isEmpty(pChars, pStart, pLength)) {
|
||||||
|
throw new SAXParseException("Unexpected non-whitespace characters",
|
||||||
|
getDocumentLocator());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sb.append(pChars, pStart, pLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void endElement(String pURI, String pLocalName, String pQName) throws SAXException {
|
||||||
|
if (--level == 0) {
|
||||||
|
setResult(sb.toString());
|
||||||
|
} else {
|
||||||
|
throw new SAXParseException("Unexpected end tag in atomic element: "
|
||||||
|
+ new QName(pURI, pLocalName),
|
||||||
|
getDocumentLocator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startElement(String pURI, String pLocalName, String pQName, Attributes pAttrs) throws SAXException {
|
||||||
|
if (level++ == 0) {
|
||||||
|
sb = new StringBuffer();
|
||||||
|
} else {
|
||||||
|
throw new SAXParseException("Unexpected start tag in atomic element: "
|
||||||
|
+ new QName(pURI, pLocalName),
|
||||||
|
getDocumentLocator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common.parser;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
import org.xml.sax.SAXParseException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parser for BigDecimal values.
|
||||||
|
*/
|
||||||
|
public class BigDecimalParser extends AtomicParser {
|
||||||
|
protected void setResult(String pResult) throws SAXException {
|
||||||
|
try {
|
||||||
|
super.setResult(new BigDecimal(pResult));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new SAXParseException("Failed to parse BigDecimal value: " + pResult,
|
||||||
|
getDocumentLocator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common.parser;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
import org.xml.sax.SAXParseException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parser for BigInteger values.
|
||||||
|
*/
|
||||||
|
public class BigIntegerParser extends AtomicParser {
|
||||||
|
protected void setResult(String pResult) throws SAXException {
|
||||||
|
try {
|
||||||
|
super.setResult(new BigInteger(pResult));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new SAXParseException("Failed to parse BigInteger value: " + pResult,
|
||||||
|
getDocumentLocator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common.parser;
|
||||||
|
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
import org.xml.sax.SAXParseException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parser for boolean values.
|
||||||
|
*/
|
||||||
|
public class BooleanParser extends AtomicParser {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setResult(String pResult) throws SAXException {
|
||||||
|
String s = pResult.trim();
|
||||||
|
if ("1".equals(s)) {
|
||||||
|
super.setResult(Boolean.TRUE);
|
||||||
|
} else if ("0".equals(s)) {
|
||||||
|
super.setResult(Boolean.FALSE);
|
||||||
|
} else {
|
||||||
|
throw new SAXParseException("Failed to parse boolean value: " + pResult,
|
||||||
|
getDocumentLocator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common.parser;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import javax.xml.namespace.QName;
|
||||||
|
|
||||||
|
import org.xml.sax.Attributes;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
import org.xml.sax.SAXParseException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A parser for base64 elements.
|
||||||
|
*/
|
||||||
|
public class ByteArrayParser extends TypeParserImpl {
|
||||||
|
private int level;
|
||||||
|
private StringBuilder sb;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startDocument() throws SAXException {
|
||||||
|
level = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void characters(char[] pChars, int pStart, int pLength) throws SAXException {
|
||||||
|
sb.append(new String(pChars, pStart, pLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void endElement(String pURI, String pLocalName, String pQName) throws SAXException {
|
||||||
|
if (--level == 0) {
|
||||||
|
setResult(Base64.getDecoder().decode(sb.toString()));
|
||||||
|
} else {
|
||||||
|
throw new SAXParseException("Unexpected end tag in atomic element: "
|
||||||
|
+ new QName(pURI, pLocalName),
|
||||||
|
getDocumentLocator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startElement(String pURI, String pLocalName, String pQName, Attributes pAttrs) throws SAXException {
|
||||||
|
if (level++ == 0) {
|
||||||
|
sb = new StringBuilder();
|
||||||
|
} else {
|
||||||
|
throw new SAXParseException("Unexpected start tag in atomic element: "
|
||||||
|
+ new QName(pURI, pLocalName),
|
||||||
|
getDocumentLocator());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package org.xbib.netty.http.xmlrpc.common.parser;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
|
||||||
|
import org.xbib.netty.http.xmlrpc.common.util.XsDateTimeFormat;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
import org.xml.sax.SAXParseException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parser for integer values.
|
||||||
|
*/
|
||||||
|
public class CalendarParser extends AtomicParser {
|
||||||
|
|
||||||
|
private static final XsDateTimeFormat format = new XsDateTimeFormat();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setResult(String pResult) throws SAXException {
|
||||||
|
try {
|
||||||
|
super.setResult(format.parseObject(pResult.trim()));
|
||||||
|
} catch (ParseException e) {
|
||||||
|
int offset = e.getErrorOffset();
|
||||||
|
final String msg;
|
||||||
|
if (offset == -1) {
|
||||||
|
msg = "Failed to parse dateTime value: " + pResult;
|
||||||
|
} else {
|
||||||
|
msg = "Failed to parse dateTime value " + pResult
|
||||||
|
+ " at position " + e.getErrorOffset();
|
||||||
|
}
|
||||||
|
throw new SAXParseException(msg, getDocumentLocator(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue