add secure cookies, xmlrpc common

This commit is contained in:
Jörg Prante 2019-07-13 15:16:09 +02:00
parent f322697702
commit 8975cd0978
151 changed files with 6904 additions and 723 deletions

View file

@ -1,6 +1,3 @@
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
plugins {
id "com.github.spotbugs" version "2.0.0"
id "org.sonarqube" version "2.6.1"
@ -8,19 +5,6 @@ plugins {
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: "io.codearte.nexus-staging"
@ -31,7 +15,6 @@ subprojects {
apply plugin: "com.github.spotbugs"
configurations {
alpnagent
asciidoclet
}
@ -39,7 +22,6 @@ subprojects {
testImplementation "org.junit.jupiter:junit-jupiter-api:${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')}"
alpnagent "org.mortbay.jetty.alpn:jetty-alpn-agent:${project.property('alpnagent.version')}"
asciidoclet "org.asciidoctor:asciidoclet:${project.property('asciidoclet.version')}"
}
@ -82,9 +64,6 @@ subprojects {
"${result.skippedTestCount} skipped"
}
}
if (JavaVersion.current() == JavaVersion.VERSION_1_8) {
jvmArgs "-javaagent:" + configurations.alpnagent.asPath
}
}
clean {

View file

@ -1,11 +1,10 @@
group = org.xbib
name = netty-http
version = 4.1.36.4
version = 4.1.36.5
# main packages
netty.version = 4.1.36.Final
tcnative.version = 2.0.25.Final
alpnagent.version = 2.0.9
# common
xbib-net-url.version = 1.3.2
@ -17,7 +16,6 @@ reactivestreams.version = 1.0.2
# server-rest
xbib-guice.version = 4.0.4
# test packages
junit.version = 5.4.2
conscrypt.version = 2.0.0

View file

@ -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.CookieHeaderNames;
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.util.Locale;
import java.util.Objects;
/**
@ -19,13 +20,12 @@ import java.util.Objects;
public final class ClientCookieDecoder extends CookieDecoder {
/**
* Strict encoder that validates that name and value chars are in the valid scope
* defined in RFC6265
* Strict encoder that validates that name and value chars are in the valid scope defined in RFC6265.
*/
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);
@ -139,27 +139,13 @@ public final class ClientCookieDecoder extends CookieDecoder {
private boolean httpOnly;
private String sameSite;
private Cookie.SameSite sameSite = Cookie.SameSite.STRICT;
CookieBuilder(DefaultCookie cookie, String header) {
this.cookie = cookie;
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.setDomain(domain);
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) {
if (header.regionMatches(true, nameStart, CookieHeaderNames.EXPIRES, 0, 7)) {
expiresStart = valueStart;
@ -231,7 +231,10 @@ public final class ClientCookieDecoder extends CookieDecoder {
if (header.regionMatches(true, nameStart, CookieHeaderNames.HTTPONLY, 0, 8)) {
httpOnly = true;
} 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;
}
private void setSameSite(String value) {
private void setSameSite(Cookie.SameSite value) {
sameSite = value;
}
}

View file

@ -99,7 +99,7 @@ public final class ClientCookieEncoder extends CookieEncoder {
* some cookies
* @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) {
return null;
}

View file

@ -66,15 +66,15 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel> {
throw new IllegalStateException();
}
};
Http2MultiplexCodecBuilder clientMultiplexCodecBuilder = Http2MultiplexCodecBuilder.forClient(initializer)
Http2MultiplexCodecBuilder multiplexCodecBuilder = Http2MultiplexCodecBuilder.forClient(initializer)
.initialSettings(clientConfig.getHttp2Settings());
if (clientConfig.isDebug()) {
clientMultiplexCodecBuilder.frameLogger(new PushPromiseHandler(LogLevel.DEBUG, "client"));
multiplexCodecBuilder.frameLogger(new PushPromiseHandler(LogLevel.DEBUG, "client"));
}
Http2MultiplexCodec http2MultiplexCodec = clientMultiplexCodecBuilder.build();
ChannelPipeline p = ch.pipeline();
p.addLast("client-codec", http2MultiplexCodec);
p.addLast("client-messages", new ClientMessages());
Http2MultiplexCodec multiplexCodec = multiplexCodecBuilder.autoAckSettingsFrame(true) .build();
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("client-multiplex", multiplexCodec);
pipeline.addLast("client-messages", new ClientMessages());
}
class ClientMessages extends ChannelInboundHandlerAdapter {

View file

@ -29,7 +29,6 @@ import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame;
import io.netty.handler.codec.http2.Http2DataFrame;
import io.netty.handler.codec.http2.Http2Exception;
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.Http2StreamChannel;
import io.netty.handler.codec.http2.Http2StreamFrame;
@ -41,8 +40,7 @@ import java.util.List;
/**
* This handler converts from {@link Http2StreamFrame} to {@link HttpObject},
* and back. It can be used as an adapter in conjunction with {@link
* Http2MultiplexCodec} to make http/2 connections backward-compatible with
* and back. It can be used as an adapter to make http/2 connections backward-compatible with
* {@link ChannelHandler}s expecting {@link HttpObject}.
*
* For simplicity, it converts to chunked encoding unless the entire stream

View file

@ -135,9 +135,11 @@ abstract class BaseTransport implements Transport {
flow.get(key).get(value, timeUnit);
} catch (Exception e) {
String requestKey = getRequestKey(entry.getKey(), key);
Request request = requests.get(requestKey);
if (request != null && request.getCompletableFuture() != null) {
request.getCompletableFuture().completeExceptionally(e);
if (requestKey != null) {
Request request = requests.get(requestKey);
if (request != null && request.getCompletableFuture() != null) {
request.getCompletableFuture().completeExceptionally(e);
}
}
flow.fail(e);
} finally {

View file

@ -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=&region=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());
}
}

View file

@ -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"));
});
}
}

View file

@ -10,7 +10,7 @@ import java.net.InetSocketAddress;
*/
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;
@ -22,7 +22,6 @@ public class HttpAddress implements PoolKey {
private InetSocketAddress inetSocketAddress;
public static HttpAddress http1(String host) {
return new HttpAddress(host, 80, HttpVersion.HTTP_1_1, false);
}
@ -86,9 +85,10 @@ public class HttpAddress implements PoolKey {
this.secure = secure;
}
@Override
public InetSocketAddress getInetSocketAddress() {
if (inetSocketAddress == null) {
// this may execute DNS lookup
// this may execute a DNS lookup, cache the result here
this.inetSocketAddress = new InetSocketAddress(host, port);
}
return inetSocketAddress;
@ -106,6 +106,7 @@ public class HttpAddress implements PoolKey {
return secure;
}
@Override
public String toString() {
return host + ":" + port + " (version:" + version + ",secure:" + secure + ")";
}

View file

@ -3,8 +3,8 @@ package org.xbib.netty.http.common;
import org.xbib.net.PercentDecoder;
import org.xbib.net.PercentEncoder;
import org.xbib.net.PercentEncoders;
import org.xbib.netty.http.common.util.LimitedSortedStringSet;
import org.xbib.netty.http.common.util.LimitedStringMap;
import org.xbib.netty.http.common.util.LimitedSet;
import org.xbib.netty.http.common.util.LimitedMap;
import java.nio.charset.MalformedInputException;
import java.nio.charset.StandardCharsets;
@ -42,7 +42,7 @@ public class HttpParameters implements Map<String, SortedSet<String>> {
private final int elementSizeLimit;
private final LimitedStringMap map;
private final LimitedMap<String, String> map;
private final PercentEncoder percentEncoder;
@ -62,7 +62,7 @@ public class HttpParameters implements Map<String, SortedSet<String>> {
this.maxParam = maxParam;
this.sizeLimit = sizeLimit;
this.elementSizeLimit = elementSizeLimit;
this.map = new LimitedStringMap(maxParam);
this.map = new LimitedMap<>(maxParam);
this.percentEncoder = PercentEncoders.getQueryEncoder(StandardCharsets.UTF_8);
this.percentDecoder = new PercentDecoder();
this.contentType = contentType;
@ -183,7 +183,7 @@ public class HttpParameters implements Map<String, SortedSet<String>> {
String k = percentEncode ? percentEncoder.encode(key) : key;
SortedSet<String> values = map.get(k);
if (values == null) {
values = new LimitedSortedStringSet(sizeLimit, elementSizeLimit);
values = new LimitedSet<>(sizeLimit, elementSizeLimit);
map.put(k, values);
}
String v = null;
@ -236,7 +236,7 @@ public class HttpParameters implements Map<String, SortedSet<String>> {
for (String key : m.keySet()) {
SortedSet<String> vals = get(key);
if (vals == null) {
vals = new LimitedSortedStringSet(sizeLimit, elementSizeLimit);
vals = new LimitedSet<>(sizeLimit, elementSizeLimit);
put(key, vals);
}
vals.addAll(m.get(key));

View file

@ -124,7 +124,29 @@ public interface Cookie extends Comparable<Cookie> {
*/
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 were 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 }
}

View file

@ -23,7 +23,7 @@ public class DefaultCookie implements Cookie {
private boolean httpOnly;
private String sameSite;
private SameSite sameSite;
/**
* Creates a new cookie with the specified name and value.
@ -38,6 +38,10 @@ public class DefaultCookie implements Cookie {
setValue(value);
}
public DefaultCookie(String name, Payload payload) {
this(name, payload.toString());
}
@Override
public String name() {
return name;
@ -114,12 +118,12 @@ public class DefaultCookie implements Cookie {
}
@Override
public void setSameSite(String sameSite) {
public void setSameSite(SameSite sameSite) {
this.sameSite = sameSite;
}
@Override
public String sameSite() {
public SameSite sameSite() {
return sameSite;
}
@ -161,7 +165,7 @@ public class DefaultCookie implements Cookie {
} else if (that.sameSite() == null) {
return false;
} 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");
}
if (sameSite() != null) {
buf.append(", SameSite=").append(sameSite());
buf.append(", SameSite=").append(sameSite().name());
}
return buf.toString();
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,5 @@
package org.xbib.netty.http.common.util;
public enum Codec {
BASE64, HEX
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -4,16 +4,16 @@ import java.util.SortedSet;
import java.util.TreeMap;
@SuppressWarnings("serial")
public class LimitedStringMap extends TreeMap<String, SortedSet<String>> {
public class LimitedMap<K, V> extends TreeMap<K, SortedSet<V>> {
private final int limit;
public LimitedStringMap(int limit) {
public LimitedMap(int limit) {
this.limit = limit;
}
@Override
public SortedSet<String> put(String key, SortedSet<String> value) {
public SortedSet<V> put(K key, SortedSet<V> value) {
if (size() < limit) {
return super.put(key, value);
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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"));
}
}

View file

@ -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.xbib.netty.http.common.HttpParameters;
import java.nio.charset.MalformedInputException;
import java.nio.charset.UnmappableCharacterException;

View file

@ -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);
}
}
}

View file

@ -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.ssl.ApplicationProtocolConfig;
@ -9,8 +9,8 @@ import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider;
import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.common.SecurityUtil;
import org.xbib.netty.http.server.ServerRequest;
import org.xbib.netty.http.server.ServerResponse;
import org.xbib.netty.http.server.endpoint.HttpEndpoint;
import org.xbib.netty.http.server.endpoint.EndpointResolver;
import org.xbib.netty.http.server.endpoint.service.Service;
import org.xbib.netty.http.server.security.tls.SelfSignedCertificate;
@ -27,50 +27,46 @@ import java.util.List;
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 {
private final HttpAddress httpAddress;
public class Domain {
private final String name;
private final SslContext sslContext;
private final Set<String> aliases;
private final List<EndpointResolver> endpointResolvers;
private final HttpAddress httpAddress;
protected NamedServer(HttpAddress httpAddress, String name, Set<String> aliases,
List<EndpointResolver> endpointResolvers) {
this(httpAddress, name, aliases, endpointResolvers, null);
}
private final SslContext sslContext;
private final List<EndpointResolver> endpointResolvers;
/**
* 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 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 sslContext SSL context or null
*/
protected NamedServer(HttpAddress httpAddress, String name, Set<String> aliases,
List<EndpointResolver> endpointResolvers,
SslContext sslContext) {
protected Domain(String name, Set<String> aliases,
HttpAddress httpAddress,
List<EndpointResolver> endpointResolvers,
SslContext sslContext) {
this.httpAddress = httpAddress;
this.name = name;
this.sslContext = sslContext;
this.aliases = aliases;
this.aliases = Collections.unmodifiableSet(aliases);
this.endpointResolvers = endpointResolvers;
}
public static Builder builder() {
return new Builder(HttpAddress.http1("localhost", 8008), "*");
return builder(HttpAddress.http1("localhost", 8008));
}
public static Builder builder(HttpAddress httpAddress) {
return new Builder(httpAddress, "*");
return builder(httpAddress, "*");
}
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)
*/
public Set<String> getAliases() {
return Collections.unmodifiableSet(aliases);
return aliases;
}
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 {
private HttpAddress httpAddress;
@ -249,24 +250,24 @@ public class NamedServer {
public Builder singleEndpoint(String path, Service service) {
addEndpointResolver(EndpointResolver.builder()
.addEndpoint(Endpoint.builder().setPath(path).addFilter(service).build()).build());
.addEndpoint(HttpEndpoint.builder().setPath(path).addFilter(service).build()).build());
return this;
}
public Builder singleEndpoint(String prefix, String path, Service service) {
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;
}
public Builder singleEndpoint(String prefix, String path, Service service, String... methods) {
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());
return this;
}
public NamedServer build() {
public Domain build() {
if (httpAddress.isSecure()) {
try {
trustManagerFactory.init(trustManagerKeyStore);
@ -281,12 +282,12 @@ public class NamedServer {
if (httpAddress.getVersion().majorVersion() == 2) {
sslContextBuilder.applicationProtocolConfig(newApplicationProtocolConfig());
}
return new NamedServer(httpAddress, serverName, aliases, endpointResolvers, sslContextBuilder.build());
return new Domain(serverName, aliases, httpAddress, endpointResolvers, sslContextBuilder.build());
} catch (Throwable t) {
throw new RuntimeException(t);
}
} else {
return new NamedServer(httpAddress, serverName, aliases, endpointResolvers);
return new Domain(serverName, aliases, httpAddress, endpointResolvers, null);
}
}

View file

@ -20,7 +20,6 @@ import io.netty.util.DomainNameMapping;
import io.netty.util.DomainNameMappingBuilder;
import org.xbib.netty.http.common.HttpAddress;
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.http2.Http2ChannelInitializer;
import org.xbib.netty.http.common.SecurityUtil;
@ -120,15 +119,15 @@ public final class Server {
}
public static Builder builder() {
return new Builder(HttpAddress.http1("localhost", 8008));
return builder(HttpAddress.http1("localhost", 8008));
}
public static Builder builder(HttpAddress httpAddress) {
return new Builder(httpAddress);
}
public static Builder builder(NamedServer namedServer) {
return new Builder(namedServer.getHttpAddress(), namedServer);
public static Builder builder(Domain domain) {
return new Builder(domain);
}
public ServerConfig getServerConfig() {
@ -142,12 +141,12 @@ public final class Server {
* the default virtual host
* @return the virtual host with the given name, or null if it doesn't exist
*/
public NamedServer getNamedServer(String name) {
return serverConfig.getNamedServers().get(name);
public Domain getNamedServer(String name) {
return serverConfig.getDomain(name);
}
public NamedServer getDefaultNamedServer() {
return serverConfig.getDefaultNamedServer();
public Domain getDefaultNamedServer() {
return serverConfig.getDefaultDomain();
}
/**
@ -235,17 +234,17 @@ public final class Server {
}
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");
}
DomainNameMapping<SslContext> domainNameMapping = null;
if (serverConfig.getAddress().isSecure() && serverConfig.getDefaultNamedServer().getSslContext() != null) {
if (serverConfig.getAddress().isSecure() && serverConfig.getDefaultDomain().getSslContext() != null) {
DomainNameMappingBuilder<SslContext> mappingBuilder =
new DomainNameMappingBuilder<>(serverConfig.getDefaultNamedServer().getSslContext());
for (NamedServer namedServer : serverConfig.getNamedServers().values()) {
String name = namedServer.getName();
new DomainNameMappingBuilder<>(serverConfig.getDefaultDomain().getSslContext());
for (Domain domain : serverConfig.getDomains()) {
String name = domain.getName();
if (!"*".equals(name)) {
mappingBuilder.add(name, namedServer.getSslContext());
mappingBuilder.add(name, domain.getSslContext());
}
}
domainNameMapping = mappingBuilder.build();
@ -292,14 +291,14 @@ public final class Server {
private ServerConfig serverConfig;
Builder(HttpAddress httpAddress) {
this(httpAddress, NamedServer.builder(httpAddress, "*").build());
private Builder(HttpAddress httpAddress) {
this(Domain.builder(httpAddress, "*").build());
}
Builder(HttpAddress httpAddress, NamedServer defaultNamedServer) {
private Builder(Domain defaultDomain) {
this.serverConfig = new ServerConfig();
this.serverConfig.setAddress(httpAddress);
this.serverConfig.add(defaultNamedServer);
this.serverConfig.setAddress(defaultDomain.getHttpAddress());
addServer(defaultDomain);
}
public Builder enableDebug() {
@ -432,8 +431,9 @@ public final class Server {
return this;
}
public Builder addServer(NamedServer namedServer) {
this.serverConfig.add(namedServer);
public Builder addServer(Domain domain) {
this.serverConfig.putDomain(domain);
logger.log(Level.FINE, "adding named server: " + domain);
return this;
}

View file

@ -5,8 +5,8 @@ import io.netty.channel.epoll.Epoll;
import io.netty.handler.codec.http2.Http2Settings;
import io.netty.handler.logging.LogLevel;
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.Map;
@ -188,10 +188,10 @@ public class ServerConfig {
private boolean installHttp2Upgrade = Defaults.INSTALL_HTTP_UPGRADE2;
private Map<String, NamedServer> namedServers;
private final Map<String, Domain> domains;
public ServerConfig() {
this.namedServers = new LinkedHashMap<>();
this.domains = new LinkedHashMap<>();
}
public ServerConfig enableDebug() {
@ -425,20 +425,41 @@ public class ServerConfig {
return http2Settings;
}
public ServerConfig add(NamedServer namedServer) {
this.namedServers.put(namedServer.getName(), namedServer);
for (String alias : namedServer.getAliases()) {
this.namedServers.put(alias, namedServer);
public ServerConfig putDomain(Domain domain) {
synchronized (domains) {
domains.put(domain.getName(), domain);
for (String alias : domain.getAliases()) {
domains.put(alias, domain);
}
}
return this;
}
public NamedServer getDefaultNamedServer() {
return namedServers.get("*");
public Collection<Domain> getDomains() {
return domains.values();
}
public Map<String, NamedServer> getNamedServers() {
return namedServers;
public ServerConfig removeDomain(String name) {
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);
}
}

View file

@ -1,24 +1,19 @@
package org.xbib.netty.http.server;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.ssl.SslContext;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import org.xbib.net.URL;
import org.xbib.netty.http.common.HttpParameters;
import org.xbib.netty.http.server.endpoint.EndpointInfo;
import javax.net.ssl.SSLSession;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
public interface ServerRequest {
ChannelHandlerContext getChannelHandlerContext();
FullHttpRequest getRequest();
URL getURL();
EndpointInfo getEndpointInfo();
@ -29,9 +24,13 @@ public interface ServerRequest {
void addPathParameter(String key, String value) throws IOException;
void createParameters() throws IOException;
Map<String, String> getPathParameters();
void createParameters() throws IOException;
HttpMethod getMethod();
HttpHeaders getHeaders();
HttpParameters getParameters();
@ -47,59 +46,6 @@ public interface ServerRequest {
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;
}
}
}

View file

@ -34,6 +34,10 @@ public interface ServerResponse {
void write(ChunkedInput<ByteBuf> chunkedInput);
static void write(ServerResponse serverResponse, int status) {
write(serverResponse, HttpResponseStatus.valueOf(status));
}
static void write(ServerResponse serverResponse, HttpResponseStatus status) {
write(serverResponse, status, "application/octet-stream", status.reasonPhrase());
}

View file

@ -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;
@ -8,26 +8,28 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* The {@code Context} annotation decorates methods which are mapped
* to a context path within the server, and provide its contents.
* The {@code Endpoint} annotation decorates methods which are mapped
* to a HTTP endpoint within the server, and provide its contents.
* The annotated methods must have the same signature and contract
* as {@link Service#handle}, but can have arbitrary names.
*/
@Retention(RetentionPolicy.RUNTIME)
@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[] contentTypes();
}

View file

@ -6,9 +6,9 @@ import org.xbib.netty.http.common.cookie.CookieHeaderNames;
import org.xbib.netty.http.common.cookie.DefaultCookie;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Objects;
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.
@ -20,13 +20,13 @@ import java.util.TreeSet;
*/
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
@ -54,7 +54,7 @@ public final class ServerCookieDecoder extends CookieDecoder {
if (headerLen == 0) {
return Collections.emptySet();
}
Set<Cookie> cookies = new TreeSet<Cookie>();
Set<Cookie> cookies = new LinkedHashSet<>();
int i = 0;
boolean rfc2965Style = false;
if (header.regionMatches(true, 0, RFC2965_VERSION, 0, RFC2965_VERSION.length())) {

View file

@ -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.CookieUtil;
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.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
@ -80,10 +83,10 @@ public final class ServerCookieEncoder extends CookieEncoder {
}
if (cookie.maxAge() != Long.MIN_VALUE) {
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(CookieUtil.EQUALS);
//DateFormatter.append(expires, buf)
buf.append(DateTimeUtils.formatMillis(expires.toEpochMilli()));
buf.append(CookieUtil.SEMICOLON);
buf.append(CookieUtil.SP);
}
@ -100,7 +103,9 @@ public final class ServerCookieEncoder extends CookieEncoder {
CookieUtil.add(buf, CookieHeaderNames.HTTPONLY);
}
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);
}

View file

@ -1,174 +1,16 @@
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 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;
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);
}
}
void resolveUriTemplate(ServerRequest serverRequest) throws IOException;
}

View file

@ -8,5 +8,5 @@ import java.io.IOException;
@FunctionalInterface
public interface EndpointDispatcher {
void dispatch(Endpoint endpoint, ServerRequest serverRequest, ServerResponse serverResponse) throws IOException;
void dispatch(HttpEndpoint endpoint, ServerRequest serverRequest, ServerResponse serverResponse) throws IOException;
}

View file

@ -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;
}
}

View file

@ -3,6 +3,7 @@ package org.xbib.netty.http.server.endpoint;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.xbib.netty.http.server.ServerRequest;
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 java.io.IOException;
@ -20,32 +21,32 @@ public class EndpointResolver {
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 LRUCache<ServerRequest.EndpointInfo, List<Endpoint>> cache;
private final LRUCache<EndpointInfo, List<HttpEndpoint>> endpointInfos;
private EndpointResolver(Endpoint defaultEndpoint,
List<Endpoint> endpoints,
private EndpointResolver(HttpEndpoint defaultEndpoint,
List<HttpEndpoint> endpoints,
EndpointDispatcher endpointDispatcher,
int cacheSize) {
this.defaultEndpoint = defaultEndpoint == null ? createDefaultEndpoint() : defaultEndpoint;
this.endpoints = endpoints;
this.endpointDispatcher = endpointDispatcher;
this.cache = new LRUCache<>(cacheSize);
this.endpointInfos = new LRUCache<>(cacheSize);
}
public void resolve(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
ServerRequest.EndpointInfo endpointInfo = serverRequest.getEndpointInfo();
cache.putIfAbsent(endpointInfo, endpoints.stream()
EndpointInfo endpointInfo = serverRequest.getEndpointInfo();
endpointInfos.putIfAbsent(endpointInfo, endpoints.stream()
.filter(endpoint -> endpoint.matches(endpointInfo))
.sorted(new Endpoint.EndpointPathComparator(endpointInfo.getPath())).collect(Collectors.toList()));
List<Endpoint> matchingEndpoints = cache.get(endpointInfo);
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "endpoint info = " + endpointInfo + " matching endpoints = " + matchingEndpoints + " cache size=" + cache.size());
.sorted(new HttpEndpoint.EndpointPathComparator(endpointInfo.getPath())).collect(Collectors.toList()));
List<HttpEndpoint> matchingEndpoints = endpointInfos.get(endpointInfo);
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "endpoint info = " + endpointInfo + " matching endpoints = " + matchingEndpoints + " cache size=" + endpointInfos.size());
}
if (matchingEndpoints.isEmpty()) {
if (defaultEndpoint != null) {
@ -58,7 +59,7 @@ public class EndpointResolver {
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_IMPLEMENTED);
}
} else {
for (Endpoint endpoint : matchingEndpoints) {
for (HttpEndpoint endpoint : matchingEndpoints) {
endpoint.resolveUriTemplate(serverRequest);
endpoint.executeFilters(serverRequest, serverResponse);
if (serverResponse.getStatus() != null) {
@ -66,7 +67,7 @@ public class EndpointResolver {
}
}
if (endpointDispatcher != null) {
for (Endpoint endpoint : matchingEndpoints) {
for (HttpEndpoint endpoint : matchingEndpoints) {
endpointDispatcher.dispatch(endpoint, serverRequest, serverResponse);
if (serverResponse.getStatus() != null) {
break;
@ -76,12 +77,12 @@ public class EndpointResolver {
}
}
public LRUCache<ServerRequest.EndpointInfo, List<Endpoint>> getCache() {
return cache;
public Map<EndpointInfo, List<HttpEndpoint>> getEndpointInfos() {
return endpointInfos;
}
protected Endpoint createDefaultEndpoint() {
return Endpoint.builder()
protected HttpEndpoint createDefaultEndpoint() {
return HttpEndpoint.builder()
.setPath("/**")
.addMethod("GET")
.addMethod("HEAD")
@ -122,9 +123,9 @@ public class EndpointResolver {
private String prefix;
private Endpoint defaultEndpoint;
private HttpEndpoint defaultEndpoint;
private List<Endpoint> endpoints;
private List<HttpEndpoint> endpoints;
private EndpointDispatcher endpointDispatcher;
@ -143,7 +144,7 @@ public class EndpointResolver {
return this;
}
public Builder setDefaultEndpoint(Endpoint endpoint) {
public Builder setDefaultEndpoint(HttpEndpoint endpoint) {
this.defaultEndpoint = endpoint;
return this;
}
@ -154,13 +155,13 @@ public class EndpointResolver {
* @param endpoint the endpoint
* @return this builder
*/
public Builder addEndpoint(Endpoint endpoint) {
public Builder addEndpoint(HttpEndpoint endpoint) {
if (endpoint.getPrefix().equals("/") && prefix != null && !prefix.isEmpty()) {
Endpoint thisEndpoint = Endpoint.builder(endpoint).setPrefix(prefix).build();
logger.log(Level.FINEST, "adding endpoint = " + thisEndpoint);
HttpEndpoint thisEndpoint = HttpEndpoint.builder(endpoint).setPrefix(prefix).build();
logger.log(Level.FINE, "adding endpoint = " + thisEndpoint);
endpoints.add(thisEndpoint);
} else {
logger.log(Level.FINEST, "adding endpoint = " + endpoint);
logger.log(Level.FINE, "adding endpoint = " + endpoint);
endpoints.add(endpoint);
}
return this;
@ -168,20 +169,22 @@ public class EndpointResolver {
/**
* 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
* @return this builder
*/
public Builder addEndpoint(Object classWithAnnotatedMethods) {
for (Class<?> clazz = classWithAnnotatedMethods.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
for (Method method : clazz.getDeclaredMethods()) {
Context context = method.getAnnotation(Context.class);
if (context != null) {
addEndpoint(Endpoint.builder()
Endpoint endpoint = method.getAnnotation(Endpoint.class);
if (endpoint != null) {
MethodService methodService = new MethodService(method, classWithAnnotatedMethods);
addEndpoint(HttpEndpoint.builder()
.setPrefix(prefix)
.setPath(context.value())
.setMethods(Arrays.asList(context.methods()))
.addFilter(new MethodService(method, classWithAnnotatedMethods))
.setPath(endpoint.path())
.setMethods(Arrays.asList(endpoint.methods()))
.setContentTypes(Arrays.asList(endpoint.contentTypes()))
.addFilter(methodService)
.build());
}
}

View file

@ -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);
}
}
}

View file

@ -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.ServerResponse;
import org.xbib.netty.http.server.endpoint.service.Service;
import java.io.IOException;
public class EmptyService implements Service {
@Override
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) {
// do nothing
}
}

View file

@ -81,23 +81,4 @@ public class FileService extends ResourceService {
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);
}
}*/
}

View file

@ -34,10 +34,8 @@ public class MethodService implements Service {
public void handle(ServerRequest serverRequest, ServerResponse serverResponse) throws IOException {
try {
m.invoke(obj, serverRequest, serverResponse);
} catch (InvocationTargetException ite) {
throw new IOException("error: " + ite.getCause().getMessage());
} catch (Exception e) {
throw new IOException("error: " + e);
throw new IOException(e);
}
}
}

View file

@ -6,7 +6,7 @@ import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
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.ServerResponse;
import org.xbib.netty.http.server.util.MimeTypeUtils;
@ -49,20 +49,20 @@ public abstract class ResourceService implements Service {
protected abstract boolean isRangeResponseEnabled();
protected void handleResource(ServerRequest serverRequest, ServerResponse serverResponse, Resource resource) {
HttpHeaders headers = serverRequest.getRequest().headers();
private void handleResource(ServerRequest serverRequest, ServerResponse serverResponse, Resource resource) {
HttpHeaders headers = serverRequest.getHeaders();
String contentType = MimeTypeUtils.guessFromPath(resource.getResourcePath(), false);
long maxAgeSeconds = 24 * 3600;
long expirationMillis = System.currentTimeMillis() + 1000 * maxAgeSeconds;
if (isCacheResponseEnabled()) {
serverResponse.withHeader(HttpHeaderNames.EXPIRES, TimeUtils.formatMillis(expirationMillis))
serverResponse.withHeader(HttpHeaderNames.EXPIRES, DateTimeUtils.formatMillis(expirationMillis))
.withHeader(HttpHeaderNames.CACHE_CONTROL, "public, max-age=" + maxAgeSeconds);
}
boolean sent = false;
if (isETagResponseEnabled()) {
Instant lastModifiedInstant = resource.getLastModified();
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 &&
ifUnmodifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) {
ServerResponse.write(serverResponse, HttpResponseStatus.PRECONDITION_FAILED);
@ -76,20 +76,20 @@ public abstract class ResourceService implements Service {
String ifNoneMatch = headers.get(HttpHeaderNames.IF_NONE_MATCH);
if (ifNoneMatch != null && matches(ifNoneMatch, eTag)) {
serverResponse.withHeader(HttpHeaderNames.ETAG, eTag)
.withHeader(HttpHeaderNames.EXPIRES, TimeUtils.formatMillis(expirationMillis));
.withHeader(HttpHeaderNames.EXPIRES, DateTimeUtils.formatMillis(expirationMillis));
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_MODIFIED);
return;
}
Instant ifModifiedSinceInstant = TimeUtils.parseDate(headers.get(HttpHeaderNames.IF_MODIFIED_SINCE));
Instant ifModifiedSinceInstant = DateTimeUtils.parseDate(headers.get(HttpHeaderNames.IF_MODIFIED_SINCE));
if (ifModifiedSinceInstant != null &&
ifModifiedSinceInstant.plusMillis(1000L).isAfter(lastModifiedInstant)) {
serverResponse.withHeader(HttpHeaderNames.ETAG, eTag)
.withHeader(HttpHeaderNames.EXPIRES, TimeUtils.formatMillis(expirationMillis));
.withHeader(HttpHeaderNames.EXPIRES, DateTimeUtils.formatMillis(expirationMillis));
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_MODIFIED);
return;
}
serverResponse.withHeader(HttpHeaderNames.ETAG, eTag)
.withHeader(HttpHeaderNames.LAST_MODIFIED, TimeUtils.formatInstant(lastModifiedInstant));
.withHeader(HttpHeaderNames.LAST_MODIFIED, DateTimeUtils.formatInstant(lastModifiedInstant));
if (isRangeResponseEnabled()) {
performRangeResponse(serverRequest, serverResponse, resource, contentType, eTag, headers);
sent = true;
@ -97,11 +97,11 @@ public abstract class ResourceService implements Service {
}
if (!sent) {
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,
String contentType, String eTag,
HttpHeaders headers) {
@ -119,7 +119,7 @@ public abstract class ResourceService implements Service {
String ifRange = headers.get(HttpHeaderNames.IF_RANGE);
if (ifRange != null && !ifRange.equals(eTag)) {
try {
Instant ifRangeTime = TimeUtils.parseDate(ifRange);
Instant ifRangeTime = DateTimeUtils.parseDate(ifRange);
if (ifRangeTime != null && ifRangeTime.plusMillis(1000).isBefore(resource.getLastModified())) {
ranges.add(full);
}
@ -180,28 +180,27 @@ public abstract class ResourceService implements Service {
return Arrays.binarySearch(matchValues, toMatch) > -1 || Arrays.binarySearch(matchValues, "*") > -1;
}
private static long sublong(String value, int beginIndex, int endIndex) {
String substring = value.substring(beginIndex, endIndex);
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) {
if (serverRequest.getRequest().method() == HttpMethod.HEAD) {
if (serverRequest.getMethod() == HttpMethod.HEAD) {
ServerResponse.write(serverResponse, HttpResponseStatus.OK, contentType);
} else {
if ("file".equals(url.getProtocol())) {
try {
send((FileChannel) Files.newByteChannel(Paths.get(url.toURI())),
httpResponseStatus, contentType, serverResponse);
HttpResponseStatus.OK, contentType, serverResponse);
} catch (URISyntaxException | IOException e) {
logger.log(Level.SEVERE, e.getMessage(), e);
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_FOUND);
}
} else {
try (InputStream inputStream = url.openStream()) {
send(inputStream, httpResponseStatus, contentType, serverResponse);
send(inputStream, HttpResponseStatus.OK, contentType, serverResponse);
} catch (IOException e) {
logger.log(Level.SEVERE, e.getMessage(), e);
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) {
if (serverRequest.getRequest().method() == HttpMethod.HEAD) {
if (serverRequest.getMethod() == HttpMethod.HEAD) {
ServerResponse.write(serverResponse, HttpResponseStatus.OK, contentType);
} else {
if ("file".equals(url.getProtocol())) {
@ -234,21 +233,21 @@ public abstract class ResourceService implements Service {
}
}
protected void send(FileChannel fileChannel, HttpResponseStatus httpResponseStatus, String contentType,
ServerResponse serverResponse) throws IOException {
private void send(FileChannel fileChannel, HttpResponseStatus httpResponseStatus, String contentType,
ServerResponse serverResponse) throws IOException {
send(fileChannel, httpResponseStatus, contentType, serverResponse, 0L, fileChannel.size());
}
protected void send(FileChannel fileChannel, HttpResponseStatus httpResponseStatus, String contentType,
ServerResponse serverResponse, long offset, long size) throws IOException {
private void send(FileChannel fileChannel, HttpResponseStatus httpResponseStatus, String contentType,
ServerResponse serverResponse, long offset, long size) throws IOException {
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, offset, size);
serverResponse.withStatus(httpResponseStatus)
.withContentType(contentType)
.write(Unpooled.wrappedBuffer(mappedByteBuffer));
}
protected void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType,
ServerResponse serverResponse) throws IOException {
private void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType,
ServerResponse serverResponse) throws IOException {
try (ReadableByteChannel channel = Channels.newChannel(inputStream)) {
serverResponse.withStatus(httpResponseStatus)
.withContentType(contentType)
@ -256,14 +255,14 @@ public abstract class ResourceService implements Service {
}
}
protected void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType,
ServerResponse serverResponse, long offset, long size) throws IOException {
private void send(InputStream inputStream, HttpResponseStatus httpResponseStatus, String contentType,
ServerResponse serverResponse, long offset, long size) throws IOException {
serverResponse.withStatus(httpResponseStatus)
.withContentType(contentType)
.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())) {
try (SeekableByteChannel channel = Files.newByteChannel(Paths.get(url.toURI()))) {
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);
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);
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);
buf.rewind();
channel.read(buf);

View file

@ -1,6 +1,7 @@
package org.xbib.netty.http.server.handler.http2;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
@ -69,64 +70,66 @@ public class Http2ChannelInitializer extends ChannelInitializer<Channel> {
} else {
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());
}
}
private void configureEncrypted(Channel channel) {
channel.pipeline().addLast("sni-handler", new SniHandler(domainNameMapping));
channel.pipeline().addLast("sni-handler", new SniHandler(domainNameMapping));
configureCleartext(channel);
}
private void configureCleartext(Channel ch) {
ChannelPipeline p = ch.pipeline();
Http2MultiplexCodecBuilder serverMultiplexCodecBuilder = Http2MultiplexCodecBuilder.forServer(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) {
Transport transport = server.newTransport(httpAddress.getVersion());
channel.attr(Transport.TRANSPORT_ATTRIBUTE_KEY).set(transport);
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast("multiplex-server-frame-converter",
new Http2StreamFrameToHttpObjectCodec(true));
if (serverConfig.isCompressionEnabled()) {
pipeline.addLast("multiplex-server-compressor", new HttpContentCompressor());
}
if (serverConfig.isDecompressionEnabled()) {
pipeline.addLast("multiplex-server-decompressor", new HttpContentDecompressor());
}
pipeline.addLast("multiplex-server-object-aggregator",
new HttpObjectAggregator(serverConfig.getMaxContentLength()));
pipeline.addLast("multiplex-server-chunked-write",
new ChunkedWriteHandler());
pipeline.addLast("multiplex-server-request-handler",
new ServerRequestHandler());
ChannelHandler channelHandler = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) {
Transport transport = server.newTransport(httpAddress.getVersion());
channel.attr(Transport.TRANSPORT_ATTRIBUTE_KEY).set(transport);
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast("server-frame-converter",
new Http2StreamFrameToHttpObjectCodec(true));
if (serverConfig.isCompressionEnabled()) {
pipeline.addLast("server-compressor", new HttpContentCompressor());
}
})
if (serverConfig.isDecompressionEnabled()) {
pipeline.addLast("server-decompressor", new HttpContentDecompressor());
}
pipeline.addLast("server-object-aggregator",
new HttpObjectAggregator(serverConfig.getMaxContentLength()));
pipeline.addLast("server-chunked-write", new ChunkedWriteHandler());
pipeline.addLast("server-request-handler", new ServerRequestHandler());
}
};
Http2MultiplexCodecBuilder multiplexCodecBuilder = Http2MultiplexCodecBuilder.forServer(channelHandler)
.initialSettings(Http2Settings.defaultSettings());
if (serverConfig.isDebug()) {
serverMultiplexCodecBuilder.frameLogger(new Http2FrameLogger(LogLevel.DEBUG, "server"));
multiplexCodecBuilder.frameLogger(new Http2FrameLogger(LogLevel.DEBUG, "server"));
}
Http2MultiplexCodec serverMultiplexCodec = serverMultiplexCodecBuilder.build();
HttpServerUpgradeHandler.UpgradeCodecFactory upgradeCodecFactory = protocol -> {
Http2MultiplexCodec multiplexCodec = multiplexCodecBuilder.build();
HttpServerCodec serverCodec = new HttpServerCodec();
HttpServerUpgradeHandler upgradeHandler = new HttpServerUpgradeHandler(serverCodec, protocol -> {
if (AsciiString.contentEquals(Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME, protocol)) {
return new Http2ServerUpgradeCodec("server-codec", serverMultiplexCodec);
return new Http2ServerUpgradeCodec(multiplexCodec);
} else {
return null;
}
};
HttpServerCodec sourceCodec = new HttpServerCodec();
HttpServerUpgradeHandler upgradeHandler = new HttpServerUpgradeHandler(sourceCodec, upgradeCodecFactory);
});
CleartextHttp2ServerUpgradeHandler cleartextHttp2ServerUpgradeHandler =
new CleartextHttp2ServerUpgradeHandler(sourceCodec, upgradeHandler, serverMultiplexCodec);
p.addLast("server-upgrade", cleartextHttp2ServerUpgradeHandler);
p.addLast("server-messages", new ServerMessages());
new CleartextHttp2ServerUpgradeHandler(serverCodec, upgradeHandler, multiplexCodec);
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("server-upgrade", cleartextHttp2ServerUpgradeHandler);
pipeline.addLast("server-messages", new ServerMessages());
}
class ServerRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override
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.requestReceived(ctx, fullHttpRequest);
}

View file

@ -30,7 +30,6 @@ import io.netty.handler.codec.http2.Http2DataFrame;
import io.netty.handler.codec.http2.Http2Exception;
import io.netty.handler.codec.http2.Http2Headers;
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.Http2StreamFrame;
import io.netty.handler.codec.http2.HttpConversionUtil;
@ -38,13 +37,10 @@ import io.netty.handler.ssl.SslHandler;
import io.netty.util.internal.UnstableApi;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This handler converts from {@link Http2StreamFrame} to {@link HttpObject},
* and back. It can be used as an adapter in conjunction with {@link
* Http2MultiplexCodec} to make http/2 connections backward-compatible with
* and back. It can be used as an adapter to make http/2 connections backward-compatible with
* {@link ChannelHandler}s expecting {@link HttpObject}.
*
* For simplicity, it converts to chunked encoding unless the entire stream
@ -54,8 +50,6 @@ import java.util.logging.Logger;
@Sharable
public class Http2StreamFrameToHttpObjectCodec extends MessageToMessageCodec<Http2StreamFrame, HttpObject> {
private static final Logger logger = Logger.getLogger(Http2StreamFrameToHttpObjectCodec.class.getName());
private final boolean isServer;
private final boolean validateHeaders;

View file

@ -58,6 +58,7 @@ abstract class HttpStreamsHandler<In extends HttpMessage, Out extends HttpMessag
/**
* Whether the given incoming message has a body.
* @param in input
* @return true if message has a body
*/
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.
* @param in input
* @param stream stream
* @return input
*/
protected abstract In createStreamedMessage(In in, Publisher<HttpContent> stream);

View file

@ -8,7 +8,7 @@ import io.netty.handler.codec.http.HttpVersion;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.ServerRequest;
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.util.concurrent.atomic.AtomicInteger;
@ -37,14 +37,14 @@ abstract class BaseTransport implements Transport {
* and required special header handling, possibly returning an
* appropriate response.
*
* @param namedServer the named server
* @param domain the named server
* @param serverRequest the request
* @param serverResponse the response
* @return whether further processing should be performed
*/
static boolean acceptRequest(NamedServer namedServer, ServerRequest serverRequest, ServerResponse serverResponse) {
HttpHeaders reqHeaders = serverRequest.getRequest().headers();
HttpVersion version = namedServer.getHttpAddress().getVersion();
static boolean acceptRequest(Domain domain, ServerRequest serverRequest, ServerResponse serverResponse) {
HttpHeaders reqHeaders = serverRequest.getHeaders();
HttpVersion version = domain.getHttpAddress().getVersion();
if (version.majorVersion() == 1 || version.majorVersion() == 2) {
if (!reqHeaders.contains(HttpHeaderNames.HOST)) {
// 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.
* @param namedServer the named server
* @param domain the named server
* @param serverRequest the request
* @param serverResponse the response (into which the response is written)
* @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
serverRequest.createParameters();
namedServer.execute(serverRequest, serverResponse);
domain.execute(serverRequest, serverResponse);
}
}

View file

@ -42,7 +42,7 @@ public class Http2ServerResponse implements ServerResponse {
private HttpResponseStatus httpResponseStatus;
public Http2ServerResponse(ServerRequest serverRequest) {
public Http2ServerResponse(HttpServerRequest serverRequest) {
Objects.requireNonNull(serverRequest);
Objects.requireNonNull(serverRequest.getChannelHandlerContext());
this.serverRequest = serverRequest;
@ -109,7 +109,7 @@ public class Http2ServerResponse implements ServerResponse {
if (!headers.contains(HttpHeaderNames.CONTENT_LENGTH) && !headers.contains(HttpHeaderNames.TRANSFER_ENCODING)) {
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.add(HttpHeaderNames.CONNECTION, "close");
}
@ -162,7 +162,7 @@ public class Http2ServerResponse implements ServerResponse {
logger.log(Level.FINEST, http2HeadersFrame::toString);
ctx.channel().write(http2HeadersFrame);
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)) {
channelFuture.addListener(ChannelFutureListener.CLOSE);
}

View file

@ -8,7 +8,7 @@ import io.netty.handler.codec.http2.Http2Settings;
import io.netty.handler.codec.http2.HttpConversionUtil;
import org.xbib.netty.http.server.Server;
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;
@ -26,9 +26,9 @@ public class Http2Transport extends BaseTransport {
@Override
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId) throws IOException {
int requestId = requestCounter.incrementAndGet();
NamedServer namedServer = server.getNamedServer(fullHttpRequest.headers().get(HttpHeaderNames.HOST));
if (namedServer == null) {
namedServer = server.getDefaultNamedServer();
Domain domain = server.getNamedServer(fullHttpRequest.headers().get(HttpHeaderNames.HOST));
if (domain == null) {
domain = server.getDefaultNamedServer();
}
Integer streamId = fullHttpRequest.headers().getInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text());
HttpServerRequest serverRequest = new HttpServerRequest();
@ -38,8 +38,8 @@ public class Http2Transport extends BaseTransport {
serverRequest.setRequestId(requestId);
serverRequest.setStreamId(streamId);
ServerResponse serverResponse = new Http2ServerResponse(serverRequest);
if (acceptRequest(namedServer, serverRequest, serverResponse)) {
handle(namedServer, serverRequest, serverResponse);
if (acceptRequest(domain, serverRequest, serverResponse)) {
handle(domain, serverRequest, serverResponse);
} else {
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE);
}

View file

@ -3,12 +3,14 @@ package org.xbib.netty.http.server.transport;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.ssl.SslContext;
import org.xbib.net.QueryParameters;
import org.xbib.net.URL;
import org.xbib.netty.http.common.HttpParameters;
import org.xbib.netty.http.server.ServerRequest;
import org.xbib.netty.http.server.endpoint.EndpointInfo;
import javax.net.ssl.SSLSession;
import java.io.IOException;
@ -18,16 +20,12 @@ import java.nio.charset.UnmappableCharacterException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The {@code HttpServerRequest} class encapsulates a single request. There must be a default constructor.
*/
public class HttpServerRequest implements ServerRequest {
private static final Logger logger = Logger.getLogger(HttpServerRequest.class.getName());
private static final String PATH_SEPARATOR = "/";
private static final CharSequence APPLICATION_FORM_URL_ENCODED = "application/x-www-form-urlencoded";
@ -60,7 +58,6 @@ public class HttpServerRequest implements ServerRequest {
this.ctx = ctx;
}
@Override
public ChannelHandlerContext getChannelHandlerContext() {
return ctx;
}
@ -70,7 +67,6 @@ public class HttpServerRequest implements ServerRequest {
this.info = new EndpointInfo(this);
}
@Override
public FullHttpRequest getRequest() {
return httpRequest;
}
@ -120,6 +116,16 @@ public class HttpServerRequest implements ServerRequest {
return pathParameters;
}
@Override
public HttpMethod getMethod() {
return httpRequest.method();
}
@Override
public HttpHeaders getHeaders() {
return httpRequest.headers();
}
@Override
public void createParameters() throws IOException {
try {
@ -182,6 +188,11 @@ public class HttpServerRequest implements ServerRequest {
return sslSession;
}
@Override
public ByteBuf getContent() {
return httpRequest.content();
}
public String toString() {
return "ServerRequest[request=" + httpRequest + "]";
}

View file

@ -45,7 +45,7 @@ public class HttpServerResponse implements ServerResponse {
private HttpResponseStatus httpResponseStatus;
public HttpServerResponse(ServerRequest serverRequest) {
public HttpServerResponse(HttpServerRequest serverRequest) {
Objects.requireNonNull(serverRequest, "serverRequest");
Objects.requireNonNull(serverRequest.getChannelHandlerContext(), "serverRequest channelHandlerContext");
this.serverRequest = serverRequest;
@ -114,7 +114,7 @@ public class HttpServerResponse implements ServerResponse {
int length = byteBuf.readableBytes();
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.add(HttpHeaderNames.CONNECTION, "close");
}
@ -164,7 +164,7 @@ public class HttpServerResponse implements ServerResponse {
logger.log(Level.FINEST, httpResponse.headers()::toString);
ctx.channel().write(httpResponse);
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)) {
channelFuture.addListener(ChannelFutureListener.CLOSE);
}

View file

@ -8,7 +8,7 @@ import io.netty.handler.codec.http2.Http2Settings;
import io.netty.handler.ssl.SslHandler;
import org.xbib.netty.http.server.Server;
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;
@ -27,9 +27,9 @@ public class HttpTransport extends BaseTransport {
public void requestReceived(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest, Integer sequenceId)
throws IOException {
int requestId = requestCounter.incrementAndGet();
NamedServer namedServer = server.getNamedServer(fullHttpRequest.headers().get(HttpHeaderNames.HOST));
if (namedServer == null) {
namedServer = server.getDefaultNamedServer();
Domain domain = server.getNamedServer(fullHttpRequest.headers().get(HttpHeaderNames.HOST));
if (domain == null) {
domain = server.getDefaultNamedServer();
}
HttpServerRequest serverRequest = new HttpServerRequest();
serverRequest.setChannelHandlerContext(ctx);
@ -41,8 +41,8 @@ public class HttpTransport extends BaseTransport {
serverRequest.setSession(sslHandler.engine().getSession());
}
HttpServerResponse serverResponse = new HttpServerResponse(serverRequest);
if (acceptRequest(namedServer, serverRequest, serverResponse)) {
handle(namedServer, serverRequest, serverResponse);
if (acceptRequest(domain, serverRequest, serverResponse)) {
handle(domain, serverRequest, serverResponse);
} else {
ServerResponse.write(serverResponse, HttpResponseStatus.NOT_ACCEPTABLE);
}

View file

@ -8,7 +8,7 @@ import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.Request;
import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.endpoint.NamedServer;
import org.xbib.netty.http.server.Domain;
import org.xbib.netty.http.server.endpoint.service.ClassLoaderService;
import java.nio.charset.StandardCharsets;
@ -26,11 +26,11 @@ class ClassloaderServiceTest {
@Test
void testSimpleClassloader() throws Exception {
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
NamedServer namedServer = NamedServer.builder(httpAddress)
Domain domain = Domain.builder(httpAddress)
.singleEndpoint("/classloader", "/**",
new ClassLoaderService(ClassloaderServiceTest.class, "/cl"))
.build();
Server server = Server.builder(namedServer)
Server server = Server.builder(domain)
.enableDebug()
.build();
server.logDiagnostics(Level.INFO);

View file

@ -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.common.HttpAddress;
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.util.concurrent.ExecutorService;
@ -30,13 +30,13 @@ class CleartextHttp1Test {
@Test
void testSimpleClearTextHttp1() throws Exception {
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
NamedServer namedServer = NamedServer.builder(httpAddress)
Domain domain = Domain.builder(httpAddress)
.singleEndpoint("/**", (request, response) ->
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
.write(request.getRequest().content().retain()))
.write(request.getContent().retain()))
.build();
Server server = Server.builder(namedServer).build();
Server server = Server.builder(domain).build();
server.accept();
Client client = Client.builder()
.build();
@ -66,13 +66,13 @@ class CleartextHttp1Test {
void testPooledClearTextHttp1() throws Exception {
int loop = 4096;
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
NamedServer namedServer = NamedServer.builder(httpAddress)
Domain domain = Domain.builder(httpAddress)
.singleEndpoint("/**", (request, response) ->
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
.write(request.getRequest().content().retain()))
.write(request.getContent().retain()))
.build();
Server server = Server.builder(namedServer).build();
Server server = Server.builder(domain).build();
server.accept();
Client client = Client.builder()
.addPoolNode(httpAddress)
@ -113,13 +113,13 @@ class CleartextHttp1Test {
int threads = 4;
int loop = 4 * 1024;
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
NamedServer namedServer = NamedServer.builder(httpAddress)
Domain domain = Domain.builder(httpAddress)
.singleEndpoint("/**", (request, response) ->
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
.write(request.getRequest().content().retain()))
.write(request.getContent().retain()))
.build();
Server server = Server.builder(namedServer).build();
Server server = Server.builder(domain).build();
server.accept();
Client client = Client.builder()
.addPoolNode(httpAddress)

View file

@ -10,7 +10,7 @@ import org.xbib.netty.http.client.transport.Transport;
import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.Server;
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.nio.charset.StandardCharsets;
@ -31,13 +31,13 @@ class CleartextHttp2Test {
@Test
void testSimpleCleartextHttp2() throws Exception {
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
NamedServer namedServer = NamedServer.builder(httpAddress)
Domain domain = Domain.builder(httpAddress)
.singleEndpoint("/", (request, response) ->
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
.write(request.getRequest().content().retain()))
.write(request.getContent().retain()))
.build();
Server server = Server.builder(namedServer).build();
Server server = Server.builder(domain).build();
server.accept();
Client client = Client.builder()
.build();
@ -73,13 +73,13 @@ class CleartextHttp2Test {
void testPooledClearTextHttp2() throws Exception {
int loop = 4096;
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
NamedServer namedServer = NamedServer.builder(httpAddress)
Domain domain = Domain.builder(httpAddress)
.singleEndpoint("/", (request, response) ->
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
.write(request.getRequest().content().retain()))
.write(request.getContent().retain()))
.build();
Server server = Server.builder(namedServer).build();
Server server = Server.builder(domain).build();
server.accept();
Client client = Client.builder()
.addPoolNode(httpAddress)
@ -123,12 +123,12 @@ class CleartextHttp2Test {
int threads = 2;
int loop = 2 * 1024;
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
NamedServer namedServer = NamedServer.builder(httpAddress)
Domain domain = Domain.builder(httpAddress)
.singleEndpoint("/", (request, response) ->
ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
request.getRequest().content().toString(StandardCharsets.UTF_8)))
request.getContent().toString(StandardCharsets.UTF_8)))
.build();
Server server = Server.builder(namedServer).build();
Server server = Server.builder(domain).build();
server.accept();
Client client = Client.builder()
.addPoolNode(httpAddress)
@ -187,25 +187,25 @@ class CleartextHttp2Test {
HttpAddress httpAddress1 = HttpAddress.http2("localhost", 8008);
AtomicInteger counter1 = new AtomicInteger();
NamedServer namedServer1 = NamedServer.builder(httpAddress1)
Domain domain1 = Domain.builder(httpAddress1)
.singleEndpoint("/", (request, response) -> {
ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
request.getRequest().content().toString(StandardCharsets.UTF_8));
request.getContent().toString(StandardCharsets.UTF_8));
counter1.incrementAndGet();
})
.build();
Server server1 = Server.builder(namedServer1).build();
Server server1 = Server.builder(domain1).build();
server1.accept();
HttpAddress httpAddress2 = HttpAddress.http2("localhost", 8009);
AtomicInteger counter2 = new AtomicInteger();
NamedServer namedServer2 = NamedServer.builder(httpAddress2)
Domain domain2 = Domain.builder(httpAddress2)
.singleEndpoint("/", (request, response) -> {
ServerResponse.write(response, HttpResponseStatus.OK, "text/plain",
request.getRequest().content().toString(StandardCharsets.UTF_8));
request.getContent().toString(StandardCharsets.UTF_8));
counter2.incrementAndGet();
})
.build();
Server server2 = Server.builder(namedServer2).build();
Server server2 = Server.builder(domain2).build();
server2.accept();
Client client = Client.builder()
.addPoolNode(httpAddress1)

View file

@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test;
import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.Server;
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.net.BindException;
@ -17,11 +17,11 @@ class DoubleServerTest {
@Test
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"))
.build();
Server server1 = Server.builder(namedServer).build();
Server server2 = Server.builder(namedServer).build();
Server server1 = Server.builder(domain).build();
Server server2 = Server.builder(domain).build();
try {
Assertions.assertThrows(BindException.class, () ->{
ChannelFuture channelFuture1 = server1.accept();

View file

@ -9,9 +9,9 @@ import org.xbib.netty.http.client.Request;
import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.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.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.Service;
@ -39,16 +39,16 @@ class EndpointTest {
Service service = new FileService(vartmp);
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
EndpointResolver endpointResolver = EndpointResolver.builder()
.addEndpoint(Endpoint.builder().setPath("/**").build())
.addEndpoint(HttpEndpoint.builder().setPath("/**").build())
.setDispatcher((endpoint, req, resp) -> {
logger.log(Level.FINE, "dispatching endpoint=" + endpoint + " req=" + req);
service.handle(req, resp);
})
.build();
NamedServer namedServer = NamedServer.builder(httpAddress)
Domain domain = Domain.builder(httpAddress)
.addEndpointResolver(endpointResolver)
.build();
Server server = Server.builder(namedServer)
Server server = Server.builder(domain)
.build();
Client client = Client.builder()
.build();
@ -79,16 +79,16 @@ class EndpointTest {
Service service = new FileService(vartmp);
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
EndpointResolver endpointResolver = EndpointResolver.builder()
.addEndpoint(Endpoint.builder().setPrefix("/").setPath("/**").build())
.addEndpoint(HttpEndpoint.builder().setPrefix("/").setPath("/**").build())
.setDispatcher((endpoint, req, resp) -> {
logger.log(Level.FINE, "dispatching endpoint=" + endpoint + " req=" + req);
service.handle(req, resp);
})
.build();
NamedServer namedServer = NamedServer.builder(httpAddress)
Domain domain = Domain.builder(httpAddress)
.addEndpointResolver(endpointResolver)
.build();
Server server = Server.builder(namedServer)
Server server = Server.builder(domain)
.build();
Client client = Client.builder()
.build();
@ -120,18 +120,18 @@ class EndpointTest {
Service service = new FileService(vartmp);
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
EndpointResolver endpointResolver = EndpointResolver.builder()
.addEndpoint(Endpoint.builder().setPrefix("/static").setPath("/**").build())
.addEndpoint(Endpoint.builder().setPrefix("/static1").setPath("/**").build())
.addEndpoint(Endpoint.builder().setPrefix("/static2").setPath("/**").build())
.addEndpoint(HttpEndpoint.builder().setPrefix("/static").setPath("/**").build())
.addEndpoint(HttpEndpoint.builder().setPrefix("/static1").setPath("/**").build())
.addEndpoint(HttpEndpoint.builder().setPrefix("/static2").setPath("/**").build())
.setDispatcher((endpoint, req, resp) -> {
logger.log(Level.FINE, "dispatching endpoint=" + endpoint + " req=" + req);
service.handle(req, resp);
})
.build();
NamedServer namedServer = NamedServer.builder(httpAddress)
Domain domain = Domain.builder(httpAddress)
.addEndpointResolver(endpointResolver)
.build();
Server server = Server.builder(namedServer)
Server server = Server.builder(domain)
.build();
Client client = Client.builder()
.build();
@ -186,19 +186,19 @@ class EndpointTest {
Service service = new FileService(vartmp);
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
EndpointResolver endpointResolver = EndpointResolver.builder()
.addEndpoint(Endpoint.builder().setPrefix("/static").setPath("/**").build())
.addEndpoint(Endpoint.builder().setPrefix("/static1").setPath("/**").build())
.addEndpoint(Endpoint.builder().setPrefix("/static2").setPath("/**").build())
.addEndpoint(HttpEndpoint.builder().setPrefix("/static").setPath("/**").build())
.addEndpoint(HttpEndpoint.builder().setPrefix("/static1").setPath("/**").build())
.addEndpoint(HttpEndpoint.builder().setPrefix("/static2").setPath("/**").build())
.setDispatcher((endpoint, req, resp) -> {
logger.log(Level.FINE, "dispatching endpoint=" + endpoint + " req=" + req +
" fragment=" + req.getURL().getFragment());
service.handle(req, resp);
})
.build();
NamedServer namedServer = NamedServer.builder(httpAddress)
Domain domain = Domain.builder(httpAddress)
.addEndpointResolver(endpointResolver)
.build();
Server server = Server.builder(namedServer)
Server server = Server.builder(domain)
.build();
Client client = Client.builder()
.build();
@ -269,15 +269,15 @@ class EndpointTest {
EndpointResolver.Builder endpointResolverBuilder = EndpointResolver.builder()
.setPrefix("/static");
for (int i = 0; i < max; i++) {
endpointResolverBuilder.addEndpoint(Endpoint.builder()
endpointResolverBuilder.addEndpoint(HttpEndpoint.builder()
.setPath("/" + i + "/**")
.addFilter((req, resp) -> ServerResponse.write(resp, HttpResponseStatus.OK))
.build());
}
NamedServer namedServer = NamedServer.builder(httpAddress)
Domain domain = Domain.builder(httpAddress)
.addEndpointResolver(endpointResolverBuilder.build())
.build();
Server server = Server.builder(namedServer)
Server server = Server.builder(domain)
.build();
Client client = Client.builder()
.build();

View file

@ -7,7 +7,7 @@ import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.Request;
import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.endpoint.NamedServer;
import org.xbib.netty.http.server.Domain;
import org.xbib.netty.http.server.endpoint.service.FileService;
import java.nio.charset.StandardCharsets;
@ -30,11 +30,10 @@ class FileServiceTest {
void testFileServiceHttp1() throws Exception {
Path vartmp = Paths.get("/var/tmp/");
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
NamedServer namedServer = NamedServer.builder(httpAddress)
Domain domain = Domain.builder(httpAddress)
.singleEndpoint("/static", "/**", new FileService(vartmp))
.build();
Server server = Server.builder(namedServer)
.enableDebug()
Server server = Server.builder(domain)
.build();
Client client = Client.builder()
.build();
@ -65,11 +64,10 @@ class FileServiceTest {
void testFileServiceHttp2() throws Exception {
Path vartmp = Paths.get("/var/tmp/");
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
NamedServer namedServer = NamedServer.builder(httpAddress)
Domain domain = Domain.builder(httpAddress)
.singleEndpoint("/static", "/**", new FileService(vartmp))
.build();
Server server = Server.builder(namedServer)
.enableDebug()
Server server = Server.builder(domain)
.build();
Client client = Client.builder()
.build();
@ -77,7 +75,8 @@ class FileServiceTest {
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"))
Request request = Request.get()
.setVersion(HttpVersion.valueOf("HTTP/2.0"))
.url(server.getServerConfig().getAddress().base().resolve("/static/test.txt"))
.build()
.setResponseListener(r -> {

View file

@ -10,7 +10,7 @@ import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.common.HttpParameters;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.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.logging.Level;
@ -26,14 +26,14 @@ class PostTest {
@Test
void testPostHttp1() throws Exception {
HttpAddress httpAddress = HttpAddress.http1("localhost", 8008);
NamedServer namedServer = NamedServer.builder(httpAddress)
Domain domain = Domain.builder(httpAddress)
.singleEndpoint("/post", "/**", (req, resp) -> {
HttpParameters parameters = req.getParameters();
logger.log(Level.INFO, "got post " + parameters.toString());
ServerResponse.write(resp, HttpResponseStatus.OK);
}, "POST")
.build();
Server server = Server.builder(namedServer)
Server server = Server.builder(domain)
.build();
Client client = Client.builder()
.build();
@ -64,14 +64,14 @@ class PostTest {
@Test
void testPostHttp2() throws Exception {
HttpAddress httpAddress = HttpAddress.http2("localhost", 8008);
NamedServer namedServer = NamedServer.builder(httpAddress)
Domain domain = Domain.builder(httpAddress)
.singleEndpoint("/post", "/**", (req, resp) -> {
HttpParameters parameters = req.getParameters();
logger.log(Level.INFO, "got post " + parameters.toString());
ServerResponse.write(resp, HttpResponseStatus.OK);
}, "POST")
.build();
Server server = Server.builder(namedServer)
Server server = Server.builder(domain)
.build();
Client client = Client.builder()
.build();

View file

@ -1,13 +1,14 @@
package org.xbib.netty.http.server.test;
import io.netty.handler.codec.http.HttpVersion;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.client.Client;
import org.xbib.netty.http.client.Request;
import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.endpoint.NamedServer;
import org.xbib.netty.http.server.Domain;
import org.xbib.netty.http.server.endpoint.service.FileService;
import java.nio.charset.StandardCharsets;
@ -26,28 +27,30 @@ class SecureFileServiceTest {
private static final Logger logger = Logger.getLogger(SecureFileServiceTest.class.getName());
@Disabled
@Test
void testSecureFileServerHttp1() throws Exception {
Path vartmp = Paths.get("/var/tmp/");
HttpAddress httpAddress = HttpAddress.secureHttp1("localhost", 8143);
Server server = Server.builder(NamedServer.builder(httpAddress, "*")
Server server = Server.builder(Domain.builder(httpAddress, "*")
.setJdkSslProvider()
.setSelfCert()
.singleEndpoint("/static", "/**", new FileService(vartmp))
.build())
.setChildThreadCount(8)
.build();
server.logDiagnostics(Level.INFO);
//server.logDiagnostics(Level.INFO);
Client client = Client.builder()
.setJdkSslProvider()
.trustInsecure()
.build();
client.logDiagnostics(Level.INFO);
//client.logDiagnostics(Level.INFO);
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.HTTP_1_1)
Request request = Request.get()
.setVersion(HttpVersion.HTTP_1_1)
.url(server.getServerConfig().getAddress().base().resolve("/static/test.txt"))
.build()
.setResponseListener(r -> {
@ -70,11 +73,56 @@ class SecureFileServiceTest {
void testSecureFileServerHttp2() throws Exception {
Path vartmp = Paths.get("/var/tmp/");
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()
.setSelfCert()
.singleEndpoint("/static", "/**", new FileService(vartmp))
.build())
.build();
Client client = Client.builder()
.setOpenSSLSslProvider()
@ -84,7 +132,8 @@ class SecureFileServiceTest {
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"))
Request request = Request.get()
.setVersion(HttpVersion.HTTP_1_1)
.url(server.getServerConfig().getAddress().base().resolve("/static/test.txt"))
.build()
.setResponseListener(r -> {

View file

@ -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.common.HttpAddress;
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.nio.charset.StandardCharsets;
@ -31,12 +31,12 @@ class SecureHttp1Test {
@Test
void testSimpleSecureHttp1() throws Exception {
HttpAddress httpAddress = HttpAddress.secureHttp1("localhost", 8143);
Server server = Server.builder(NamedServer.builder(httpAddress)
Server server = Server.builder(Domain.builder(httpAddress)
.setSelfCert()
.singleEndpoint("/", (request, response) ->
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
.write(request.getRequest().content().retain()))
.write(request.getContent().retain()))
.build())
.build();
Client client = Client.builder()
@ -66,12 +66,12 @@ class SecureHttp1Test {
void testPooledSecureHttp1() throws Exception {
int loop = 4096;
HttpAddress httpAddress = HttpAddress.secureHttp1("localhost", 8143);
Server server = Server.builder(NamedServer.builder(httpAddress)
Server server = Server.builder(Domain.builder(httpAddress)
.setSelfCert()
.singleEndpoint("/", (request, response) ->
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
.write(request.getRequest().content().retain()))
.write(request.getContent().retain()))
.build())
.build();
server.accept();
@ -114,12 +114,12 @@ class SecureHttp1Test {
int threads = 4;
int loop = 4 * 1024;
HttpAddress httpAddress = HttpAddress.secureHttp1("localhost", 8143);
Server server = Server.builder(NamedServer.builder(httpAddress)
Server server = Server.builder(Domain.builder(httpAddress)
.setSelfCert()
.singleEndpoint("/", (request, response) ->
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
.write(request.getRequest().content().retain()))
.write(request.getContent().retain()))
.build())
.build();
server.accept();

View file

@ -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.common.HttpAddress;
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.nio.charset.StandardCharsets;
@ -30,12 +30,12 @@ class SecureHttp2Test {
@Test
void testSimpleSecureHttp2() throws Exception {
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
Server server = Server.builder(NamedServer.builder(httpAddress)
Server server = Server.builder(Domain.builder(httpAddress)
.setSelfCert()
.singleEndpoint("/", (request, response) ->
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
.write(request.getRequest().content().retain()))
.write(request.getContent().retain()))
.build())
.build();
server.accept();
@ -73,12 +73,12 @@ class SecureHttp2Test {
void testPooledSecureHttp2() throws Exception {
int loop = 4096;
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
Server server = Server.builder(NamedServer.builder(httpAddress)
Server server = Server.builder(Domain.builder(httpAddress)
.setSelfCert()
.singleEndpoint("/", (request, response) ->
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
.write(request.getRequest().content().retain()))
.write(request.getContent().retain()))
.build())
.build();
server.accept();
@ -124,12 +124,12 @@ class SecureHttp2Test {
int threads = 4;
int loop = 4 * 1024;
HttpAddress httpAddress = HttpAddress.secureHttp2("localhost", 8143);
Server server = Server.builder(NamedServer.builder(httpAddress)
Server server = Server.builder(Domain.builder(httpAddress)
.setSelfCert()
.singleEndpoint("/", (request, response) ->
response.withStatus(HttpResponseStatus.OK)
.withContentType("text/plain")
.write(request.getRequest().content().retain())
.write(request.getContent().retain())
)
.build())
.build();

View file

@ -5,17 +5,17 @@ import org.junit.jupiter.api.Test;
import org.xbib.netty.http.common.HttpAddress;
import org.xbib.netty.http.server.Server;
import org.xbib.netty.http.server.ServerResponse;
import org.xbib.netty.http.server.endpoint.NamedServer;
import org.xbib.netty.http.server.Domain;
@Disabled
class ServerTest {
@Test
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"))
.build();
Server server = Server.builder(namedServer).build();
Server server = Server.builder(domain).build();
try {
server.accept().channel().closeFuture().sync();
} finally {

View file

@ -7,7 +7,7 @@ import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.xbib.netty.http.server.Server;
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.util.Set;
@ -22,10 +22,10 @@ class ThreadLeakTest {
@Test
void testForLeaks() throws IOException {
NamedServer namedServer = NamedServer.builder()
Domain domain = Domain.builder()
.singleEndpoint("/", (request, response) -> ServerResponse.write(response, "Hello World"))
.build();
Server server = Server.builder(namedServer)
Server server = Server.builder(domain)
.setByteBufAllocator(UnpooledByteBufAllocator.DEFAULT)
.build();
try {

View file

@ -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());
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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());
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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 {
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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());
}
}
}

View file

@ -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());
}
}
}

View file

@ -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());
}
}
}

View file

@ -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());
}
}
}

View file

@ -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());
}
}
}

View file

@ -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