Skip to content

Commit

Permalink
WFLY-11626 Reduce unnecessary per-request array allocation/copies whe…
Browse files Browse the repository at this point in the history
…n parsing jsessionid
  • Loading branch information
pferraro committed Jan 24, 2019
1 parent 0cef7fb commit 77c044d
Show file tree
Hide file tree
Showing 12 changed files with 239 additions and 63 deletions.
Expand Up @@ -40,20 +40,14 @@ public DistributableSessionIdentifierCodec(RouteLocator locator, RoutingSupport
this.routing = routing;
}

/**
* {@inheritDoc}
*/
@Override
public String encode(String sessionId) {
String route = this.locator.locate(sessionId);
public CharSequence encode(CharSequence sessionId) {
String route = this.locator.locate(sessionId.toString());
return (route != null) ? this.routing.format(sessionId, route) : sessionId;
}

/**
* {@inheritDoc}
*/
@Override
public String decode(String encodedSessionId) {
public CharSequence decode(CharSequence encodedSessionId) {
return this.routing.parse(encodedSessionId).getKey();
}
}
Expand Up @@ -49,7 +49,7 @@ public void encode() {

when(this.locator.locate(sessionId)).thenReturn(null);

String result = this.codec.encode(sessionId);
CharSequence result = this.codec.encode(sessionId);

assertSame(sessionId, result);

Expand All @@ -72,7 +72,7 @@ public void decode() {

when(this.routing.parse(encodedSessionId)).thenReturn(new SimpleImmutableEntry<>(sessionId, route));

String result = this.codec.decode(encodedSessionId);
CharSequence result = this.codec.decode(encodedSessionId);

assertSame(sessionId, result);
}
Expand Down
Expand Up @@ -88,7 +88,7 @@ private HttpHandler setupRoutes(HttpHandler handler) {
exchange.addResponseCommitListener(ex -> {
Cookie cookie = ex.getResponseCookies().get(JSESSIONID);
if(cookie != null ) {
cookie.setValue(codec.encode(cookie.getValue()));
cookie.setValue(codec.encode(cookie.getValue()).toString());
}
});
handler.handleRequest(exchange);
Expand Down
Expand Up @@ -33,10 +33,10 @@
* @author Paul Ferraro
*/
public class CodecSessionConfig implements SessionConfig {
private static final AttachmentKey<Boolean> SESSION_ID_SET = AttachmentKey.create(Boolean.class);

private final SessionConfig config;
private final SessionIdentifierCodec codec;
private static final AttachmentKey<Boolean> SESSION_ID_SET = AttachmentKey.create(Boolean.class);

public CodecSessionConfig(SessionConfig config, SessionIdentifierCodec codec) {
this.config = config;
Expand All @@ -46,25 +46,25 @@ public CodecSessionConfig(SessionConfig config, SessionIdentifierCodec codec) {
@Override
public void setSessionId(HttpServerExchange exchange, String sessionId) {
exchange.putAttachment(SESSION_ID_SET, Boolean.TRUE);
this.config.setSessionId(exchange, this.codec.encode(sessionId));
this.config.setSessionId(exchange, this.codec.encode(sessionId).toString());
}

@Override
public void clearSession(HttpServerExchange exchange, String sessionId) {
this.config.clearSession(exchange, this.codec.encode(sessionId));
this.config.clearSession(exchange, this.codec.encode(sessionId).toString());
}

@Override
public String findSessionId(HttpServerExchange exchange) {
String encodedSessionId = this.config.findSessionId(exchange);
if (encodedSessionId == null) return null;
String sessionId = this.codec.decode(encodedSessionId);
CharSequence sessionId = this.codec.decode(encodedSessionId);
// Check if the encoding for this session has changed
String reencodedSessionId = this.codec.encode(sessionId);
if (!reencodedSessionId.equals(encodedSessionId) && exchange.getAttachment(SESSION_ID_SET) == null) {
this.config.setSessionId(exchange, reencodedSessionId);
CharSequence reencodedSessionId = this.codec.encode(sessionId);
if ((exchange.getAttachment(SESSION_ID_SET) == null) && !encodedSessionId.contentEquals(reencodedSessionId)) {
this.config.setSessionId(exchange, reencodedSessionId.toString());
}
return sessionId;
return sessionId.toString();
}

@Override
Expand All @@ -74,6 +74,6 @@ public SessionCookieSource sessionCookieSource(HttpServerExchange exchange) {

@Override
public String rewriteUrl(String originalUrl, String sessionId) {
return this.config.rewriteUrl(originalUrl, this.codec.encode(sessionId));
return this.config.rewriteUrl(originalUrl, this.codec.encode(sessionId).toString());
}
}
@@ -0,0 +1,102 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2019, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/

package org.jboss.as.web.session;

import java.nio.CharBuffer;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
* A {@link CharSequence} composed of other character sequences.
* All methods delegate to one or more of the underlying character sequences, using relative indexes.
* @author Paul Ferraro
*/
class CompositeCharSequence implements CharSequence {
private final List<CharSequence> sequences;

CompositeCharSequence(CharSequence... sequences) {
this(Arrays.asList(sequences));
}

CompositeCharSequence(List<CharSequence> sequences) {
this.sequences = sequences;
}

@Override
public int length() {
int length = 0;
for (CharSequence sequence : this.sequences) {
length += sequence.length();
}
return length;
}

@Override
public char charAt(int index) {
int relativeIndex = index;
for (CharSequence sequence : this.sequences) {
if (relativeIndex < sequence.length()) {
return sequence.charAt(relativeIndex);
}
relativeIndex -= sequence.length();
}
throw new IndexOutOfBoundsException();
}

@Override
public CharSequence subSequence(int start, int end) {
if ((start < 0) || (start > end) || (end > this.length())) {
throw new IndexOutOfBoundsException();
}
if (start == end) return "";
List<CharSequence> result = null;
int relativeStart = start;
int relativeEnd = end;
for (CharSequence sequence : this.sequences) {
if ((relativeStart < sequence.length()) && (relativeEnd > 0)) {
CharSequence subSequence = sequence.subSequence(Math.max(relativeStart, 0), Math.min(relativeEnd, sequence.length()));
if (result == null) {
// If subsequence falls within a single sequence
if ((relativeStart >= 0) && (relativeEnd <= sequence.length())) {
return subSequence;
}
result = new LinkedList<>();
}
result.add(subSequence);
}
relativeStart -= sequence.length();
relativeEnd -= sequence.length();
}
return new CompositeCharSequence(result);
}

@Override
public String toString() {
CharBuffer buffer = CharBuffer.allocate(this.length());
for (CharSequence sequence : this.sequences) {
buffer.put(CharBuffer.wrap(sequence));
}
return String.valueOf(buffer.array());
}
}
Expand Up @@ -33,13 +33,13 @@ public interface RoutingSupport {
* @param requestedSessionId the requested session identifier.
* @return a map entry containing the session ID and routing information as the key and value, respectively.
*/
Map.Entry<String, String> parse(String requestedSessionId);
Map.Entry<CharSequence, CharSequence> parse(CharSequence requestedSessionId);

/**
* Formats the specified session identifier and route identifier into a single identifier.
* @param sessionId a session identifier
* @param route a route identifier.
* @return a single identifier containing the specified session identifier and routing identifier.
*/
String format(String sessionId, String route);
CharSequence format(CharSequence sessionId, CharSequence route);
}
Expand Up @@ -19,7 +19,7 @@

/**
* Encapsulates logic to encode/decode additional information in/from a session identifier.
* Both the {@link #encode(String)} and {@link #decode(String)} methods should be idempotent.
* Both the {@link #encode(CharSequence)} and {@link #decode(CharSequence)} methods should be idempotent.
* The codec methods should also be symmetrical. i.e. the result of
* <code>decode(encode(x))</code> should yield <code>x</code>, just as the result of
* <code>encode(decode(y))</code> should yield <code>y</code>.
Expand All @@ -31,12 +31,12 @@ public interface SessionIdentifierCodec {
* @param sessionId a session identifier
* @return an encoded session identifier
*/
String encode(String sessionId);
CharSequence encode(CharSequence sessionId);

/**
* Decodes the specified session identifier encoded via {@link #encode(String)}.
* Decodes the specified session identifier encoded via {@link #encode(CharSequence)}.
* @param encodedSessionId an encoded session identifier
* @return the decoded session identifier
*/
String decode(String encodedSessionId);
CharSequence decode(CharSequence encodedSessionId);
}
Expand Up @@ -21,6 +21,7 @@
*/
package org.jboss.as.web.session;

import java.nio.CharBuffer;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Map;

Expand All @@ -31,34 +32,27 @@
*/
public class SimpleRoutingSupport implements RoutingSupport {

public static final String DEFAULT_DELIMITER = ".";

private final String delimiter;

public SimpleRoutingSupport() {
this(DEFAULT_DELIMITER);
}

public SimpleRoutingSupport(String delimiter) {
this.delimiter = delimiter;
}
private static final String DELIMITER = ".";

@Override
public Map.Entry<String, String> parse(String id) {
int index = (id != null) ? id.indexOf(this.delimiter) : -1;
return (index < 0) ? new SimpleImmutableEntry<String, String>(id, null) : new SimpleImmutableEntry<>(id.substring(0, index), id.substring(index + this.delimiter.length()));
public Map.Entry<CharSequence, CharSequence> parse(CharSequence id) {
if (id != null) {
int length = id.length();
int delimiterLength = DELIMITER.length();
for (int i = 0; i <= length - delimiterLength; ++i) {
int routeStart = i + delimiterLength;
if (DELIMITER.contentEquals(id.subSequence(i, routeStart))) {
return new SimpleImmutableEntry<>(CharBuffer.wrap(id, 0, i), CharBuffer.wrap(id, routeStart, length));
}
}
}
return new SimpleImmutableEntry<>(id, null);
}

@Override
public String format(String sessionId, String routeId) {
if ((routeId != null) && !routeId.isEmpty()) {
StringBuilder sb = new StringBuilder(sessionId.length() + delimiter.length() + routeId.length());
sb.append(sessionId);
sb.append(this.delimiter);
sb.append(routeId);
return sb.toString();
} else {
return sessionId;
}
public CharSequence format(CharSequence sessionId, CharSequence routeId) {
if ((routeId == null) || (routeId.length() == 0)) return sessionId;

return new CompositeCharSequence(sessionId, DELIMITER, routeId);
}
}
Expand Up @@ -36,12 +36,12 @@ public SimpleSessionIdentifierCodec(RoutingSupport routing, String route) {
}

@Override
public String encode(String sessionId) {
public CharSequence encode(CharSequence sessionId) {
return (this.route != null) ? this.routing.format(sessionId, this.route) : sessionId;
}

@Override
public String decode(String encodedSessionId) {
public CharSequence decode(CharSequence encodedSessionId) {
return (encodedSessionId != null) ? this.routing.parse(encodedSessionId).getKey() : null;
}
}
@@ -0,0 +1,75 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2019, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/

package org.jboss.as.web.session;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import java.util.stream.IntStream;

import org.junit.Test;

/**
* Unit test for {@link CompositeCharSequence}.
* @author Paul Ferraro
*/
public class CompositeCharSequenceTestCase {
@Test
public void test() {
CharSequence sequence = new CompositeCharSequence("01", "23", "45");
assertEquals(6, sequence.length());
assertEquals("012345", sequence.toString());

for (int i = -1; i < 7; ++i) {
try {
char result = sequence.charAt(i);
if ((i < 0) || (i >= 6)) {
fail(String.format("charAt(%d) returned '%s', but IndexOutOfBoundsException was expected", i, result));
}
assertEquals('0' + i, result);
} catch (IndexOutOfBoundsException e) {
if ((i >= 0) && (i < 6)) {
fail(String.format("Unexpected IndexOutOfBoundsException during charAt(%d)", i));
}
}
}

for (int i = -1; i < 7; ++i) {
for (int j = -1; j <= 7; ++j) {
try {
CharSequence result = sequence.subSequence(i, j);
if ((i < 0) || (i > j) || (j > 6)) {
fail(String.format("subSequence(%d, %d) returned '%s', but IndexOutOfBoundsException was expected", i, j, result.toString()));
}
StringBuilder expected = new StringBuilder(j - i);
IntStream.range(i, j).forEach(value -> expected.append((char) ('0' + value)));
assertEquals(expected.toString(), result.toString());
} catch (IndexOutOfBoundsException e) {
if ((i >= 0) && (j <= 6) && (i <= j)) {
fail(String.format("Unexpected IndexOutOfBoundsException during subSequence(%d, %d)", i, j));
}
}
}
}
}
}

0 comments on commit 77c044d

Please sign in to comment.