413154 ContextHandlerCollection defers virtual host handling to ContextHandler
This commit is contained in:
parent
8946b4946b
commit
e4ef8ee1f4
|
@ -884,8 +884,10 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
String vhost = normalizeHostname(baseRequest.getServerName());
|
||||
|
||||
boolean match = false;
|
||||
boolean connectorName = false;
|
||||
boolean connectorMatch = false;
|
||||
|
||||
loop: for (String contextVhost:_vhosts)
|
||||
for (String contextVhost:_vhosts)
|
||||
{
|
||||
if (contextVhost == null || contextVhost.length()==0)
|
||||
continue;
|
||||
|
@ -895,21 +897,21 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
case '*':
|
||||
if (contextVhost.startsWith("*."))
|
||||
// wildcard only at the beginning, and only for one additional subdomain level
|
||||
match = contextVhost.regionMatches(true,2,vhost,vhost.indexOf(".") + 1,contextVhost.length() - 2);
|
||||
match = match || contextVhost.regionMatches(true,2,vhost,vhost.indexOf(".") + 1,contextVhost.length() - 2);
|
||||
break;
|
||||
case '@':
|
||||
connectorName=true;
|
||||
String name=baseRequest.getHttpChannel().getConnector().getName();
|
||||
match=name!=null && contextVhost.length()==name.length()+1 && contextVhost.endsWith(name);
|
||||
boolean m=name!=null && contextVhost.length()==name.length()+1 && contextVhost.endsWith(name);
|
||||
match = match || m;
|
||||
connectorMatch = connectorMatch || m;
|
||||
break;
|
||||
default:
|
||||
match = contextVhost.equalsIgnoreCase(vhost);
|
||||
match = match || contextVhost.equalsIgnoreCase(vhost);
|
||||
}
|
||||
|
||||
if (match)
|
||||
break loop;
|
||||
|
||||
}
|
||||
if (!match)
|
||||
if (!match || connectorName && !connectorMatch)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1287,8 +1289,26 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
*/
|
||||
public void setContextPath(String contextPath)
|
||||
{
|
||||
if (contextPath != null && contextPath.length() > 1 && contextPath.endsWith("/"))
|
||||
throw new IllegalArgumentException("ends with /");
|
||||
if (contextPath == null)
|
||||
throw new IllegalArgumentException("null contextPath");
|
||||
|
||||
if (contextPath.endsWith("/*"))
|
||||
{
|
||||
LOG.warn(this+" contextPath ends with /*");
|
||||
contextPath=contextPath.substring(0,contextPath.length()-2);
|
||||
}
|
||||
else if (contextPath.endsWith("/"))
|
||||
{
|
||||
LOG.warn(this+" contextPath ends with /");
|
||||
contextPath=contextPath.substring(0,contextPath.length()-1);
|
||||
}
|
||||
|
||||
if (contextPath.length()==0)
|
||||
{
|
||||
LOG.warn("Empty contextPath");
|
||||
contextPath="/";
|
||||
}
|
||||
|
||||
_contextPath = contextPath;
|
||||
|
||||
if (getServer() != null && (getServer().isStarting() || getServer().isStarted()))
|
||||
|
|
|
@ -19,19 +19,19 @@
|
|||
package org.eclipse.jetty.server.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.PathMap;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HandlerContainer;
|
||||
import org.eclipse.jetty.server.HttpChannelState;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.util.LazyList;
|
||||
import org.eclipse.jetty.util.ArrayTernaryTrie;
|
||||
import org.eclipse.jetty.util.ArrayUtil;
|
||||
import org.eclipse.jetty.util.Trie;
|
||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||
import org.eclipse.jetty.util.annotation.ManagedOperation;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
|
@ -53,7 +53,7 @@ public class ContextHandlerCollection extends HandlerCollection
|
|||
{
|
||||
private static final Logger LOG = Log.getLogger(ContextHandlerCollection.class);
|
||||
|
||||
private volatile PathMap<Object> _contextMap;
|
||||
private volatile Trie<ContextHandler[]> _contexts;
|
||||
private Class<? extends ContextHandler> _contextClass = ContextHandler.class;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
@ -70,90 +70,71 @@ public class ContextHandlerCollection extends HandlerCollection
|
|||
@ManagedOperation("update the mapping of context path to context")
|
||||
public void mapContexts()
|
||||
{
|
||||
PathMap<Object> contextMap = new PathMap<Object>();
|
||||
Handler[] branches = getHandlers();
|
||||
|
||||
|
||||
for (int b=0;branches!=null && b<branches.length;b++)
|
||||
int capacity=512;
|
||||
|
||||
// Loop until we have a big enough trie to hold all the context paths
|
||||
Trie<ContextHandler[]> trie;
|
||||
loop: while(true)
|
||||
{
|
||||
Handler[] handlers=null;
|
||||
trie=new ArrayTernaryTrie<>(false,capacity);
|
||||
|
||||
if (branches[b] instanceof ContextHandler)
|
||||
Handler[] branches = getHandlers();
|
||||
|
||||
// loop over each group of contexts
|
||||
for (int b=0;branches!=null && b<branches.length;b++)
|
||||
{
|
||||
handlers = new Handler[]{ branches[b] };
|
||||
}
|
||||
else if (branches[b] instanceof HandlerContainer)
|
||||
{
|
||||
handlers = ((HandlerContainer)branches[b]).getChildHandlersByClass(ContextHandler.class);
|
||||
}
|
||||
else
|
||||
continue;
|
||||
Handler[] handlers=null;
|
||||
|
||||
for (int i=0;i<handlers.length;i++)
|
||||
{
|
||||
ContextHandler handler=(ContextHandler)handlers[i];
|
||||
|
||||
String contextPath=handler.getContextPath();
|
||||
|
||||
if (contextPath==null || contextPath.indexOf(',')>=0 || contextPath.startsWith("*"))
|
||||
throw new IllegalArgumentException ("Illegal context spec:"+contextPath);
|
||||
|
||||
if(!contextPath.startsWith("/"))
|
||||
contextPath='/'+contextPath;
|
||||
|
||||
if (contextPath.length()>1)
|
||||
if (branches[b] instanceof ContextHandler)
|
||||
{
|
||||
if (contextPath.endsWith("/"))
|
||||
contextPath+="*";
|
||||
else if (!contextPath.endsWith("/*"))
|
||||
contextPath+="/*";
|
||||
handlers = new Handler[]{ branches[b] };
|
||||
}
|
||||
|
||||
Object contexts=contextMap.get(contextPath);
|
||||
String[] vhosts=handler.getVirtualHosts();
|
||||
|
||||
|
||||
if (vhosts!=null && vhosts.length>0)
|
||||
else if (branches[b] instanceof HandlerContainer)
|
||||
{
|
||||
Map<String, Object> hosts;
|
||||
|
||||
if (contexts instanceof Map)
|
||||
hosts=(Map<String, Object>)contexts;
|
||||
else
|
||||
{
|
||||
hosts=new HashMap<String, Object>();
|
||||
hosts.put("*",contexts);
|
||||
contextMap.put(contextPath, hosts);
|
||||
}
|
||||
|
||||
for (int j=0;j<vhosts.length;j++)
|
||||
{
|
||||
String vhost=vhosts[j];
|
||||
contexts=hosts.get(vhost);
|
||||
contexts=LazyList.add(contexts,branches[b]);
|
||||
hosts.put(vhost,contexts);
|
||||
}
|
||||
}
|
||||
else if (contexts instanceof Map)
|
||||
{
|
||||
Map<String, Object> hosts=(Map<String, Object>)contexts;
|
||||
contexts=hosts.get("*");
|
||||
contexts= LazyList.add(contexts, branches[b]);
|
||||
hosts.put("*",contexts);
|
||||
handlers = ((HandlerContainer)branches[b]).getChildHandlersByClass(ContextHandler.class);
|
||||
}
|
||||
else
|
||||
continue;
|
||||
|
||||
// for each context handler in a group
|
||||
for (int i=0;handlers!=null && i<handlers.length;i++)
|
||||
{
|
||||
contexts= LazyList.add(contexts, branches[b]);
|
||||
contextMap.put(contextPath, contexts);
|
||||
ContextHandler handler=(ContextHandler)handlers[i];
|
||||
String contextPath=handler.getContextPath().substring(1);
|
||||
ContextHandler[] contexts=trie.get(contextPath);
|
||||
|
||||
if (!trie.put(contextPath,ArrayUtil.addToArray(contexts,handler,ContextHandler.class)))
|
||||
{
|
||||
capacity+=512;
|
||||
continue loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Sort the contexts so those with virtual hosts are considered before those without
|
||||
for (String ctx : trie.keySet())
|
||||
{
|
||||
ContextHandler[] contexts=trie.get(ctx);
|
||||
ContextHandler[] sorted=new ContextHandler[contexts.length];
|
||||
int i=0;
|
||||
for (ContextHandler handler:contexts)
|
||||
if (handler.getVirtualHosts()!=null && handler.getVirtualHosts().length>0)
|
||||
sorted[i++]=handler;
|
||||
for (ContextHandler handler:contexts)
|
||||
if (handler.getVirtualHosts()==null || handler.getVirtualHosts().length==0)
|
||||
sorted[i++]=handler;
|
||||
trie.put(ctx,sorted);
|
||||
}
|
||||
_contextMap=contextMap;
|
||||
|
||||
//for (String ctx : trie.keySet())
|
||||
// System.err.printf("'%s'->%s%n",ctx,Arrays.asList(trie.get(ctx)));
|
||||
_contexts=trie;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
* @see org.eclipse.jetty.server.server.handler.HandlerCollection#setHandlers(org.eclipse.jetty.server.server.Handler[])
|
||||
|
@ -161,7 +142,7 @@ public class ContextHandlerCollection extends HandlerCollection
|
|||
@Override
|
||||
public void setHandlers(Handler[] handlers)
|
||||
{
|
||||
_contextMap=null;
|
||||
_contexts=null;
|
||||
super.setHandlers(handlers);
|
||||
if (isStarted())
|
||||
mapContexts();
|
||||
|
@ -199,67 +180,31 @@ public class ContextHandlerCollection extends HandlerCollection
|
|||
}
|
||||
|
||||
// data structure which maps a request to a context; first-best match wins
|
||||
// { context path =>
|
||||
// { virtual host => context }
|
||||
// { context path => [ context ] }
|
||||
// }
|
||||
PathMap<Object> map = _contextMap;
|
||||
if (map!=null && target!=null && target.startsWith("/"))
|
||||
if (target.startsWith("/"))
|
||||
{
|
||||
// first, get all contexts matched by context path
|
||||
Object contexts = map.getLazyMatches(target);
|
||||
int limit = target.length()-1;
|
||||
|
||||
for (int i=0; i<LazyList.size(contexts); i++)
|
||||
{
|
||||
// then, match against the virtualhost of each context
|
||||
Map.Entry entry = (Map.Entry)LazyList.get(contexts, i);
|
||||
Object list = entry.getValue();
|
||||
while (limit>=0)
|
||||
{
|
||||
// Get best match
|
||||
ContextHandler[] contexts = _contexts.getBest(target,1,limit);
|
||||
if (contexts==null)
|
||||
break;
|
||||
|
||||
if (list instanceof Map)
|
||||
{
|
||||
Map hosts = (Map)list;
|
||||
String host = normalizeHostname(request.getServerName());
|
||||
|
||||
// explicitly-defined virtual hosts, most specific
|
||||
list=hosts.get(host);
|
||||
for (int j=0; j<LazyList.size(list); j++)
|
||||
{
|
||||
Handler handler = (Handler)LazyList.get(list,j);
|
||||
handler.handle(target,baseRequest, request, response);
|
||||
if (baseRequest.isHandled())
|
||||
return;
|
||||
}
|
||||
|
||||
// wildcard for one level of names
|
||||
list=hosts.get("*."+host.substring(host.indexOf(".")+1));
|
||||
for (int j=0; j<LazyList.size(list); j++)
|
||||
{
|
||||
Handler handler = (Handler)LazyList.get(list,j);
|
||||
handler.handle(target,baseRequest, request, response);
|
||||
if (baseRequest.isHandled())
|
||||
return;
|
||||
}
|
||||
|
||||
// no virtualhosts defined for the context, least specific
|
||||
// will handle any request that does not match to a specific virtual host above
|
||||
list=hosts.get("*");
|
||||
for (int j=0; j<LazyList.size(list); j++)
|
||||
{
|
||||
Handler handler = (Handler)LazyList.get(list,j);
|
||||
handler.handle(target,baseRequest, request, response);
|
||||
if (baseRequest.isHandled())
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int j=0; j<LazyList.size(list); j++)
|
||||
{
|
||||
Handler handler = (Handler)LazyList.get(list,j);
|
||||
handler.handle(target,baseRequest, request, response);
|
||||
if (baseRequest.isHandled())
|
||||
return;
|
||||
}
|
||||
}
|
||||
int l=contexts[0].getContextPath().length();
|
||||
if (l==1 || target.length()==l || target.charAt(l)=='/')
|
||||
{
|
||||
for (ContextHandler handler : contexts)
|
||||
{
|
||||
handler.handle(target,baseRequest, request, response);
|
||||
if (baseRequest.isHandled())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
limit=l-2;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -320,16 +265,5 @@ public class ContextHandlerCollection extends HandlerCollection
|
|||
_contextClass = contextClass;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
private String normalizeHostname( String host )
|
||||
{
|
||||
if ( host == null )
|
||||
return null;
|
||||
|
||||
if ( host.endsWith( "." ) )
|
||||
return host.substring( 0, host.length() -1);
|
||||
|
||||
return host;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ import static org.junit.Assert.assertFalse;
|
|||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -32,60 +34,121 @@ import org.eclipse.jetty.server.Connector;
|
|||
import org.eclipse.jetty.server.LocalConnector;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.util.ArrayUtil;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ContextHandlerCollectionTest
|
||||
{
|
||||
@Test
|
||||
public void testVirtualHostNormalization() throws Exception
|
||||
public void testVirtualHosts() throws Exception
|
||||
{
|
||||
Server server = new Server();
|
||||
LocalConnector connector = new LocalConnector(server);
|
||||
LocalConnector connector0 = new LocalConnector(server);
|
||||
LocalConnector connector1 = new LocalConnector(server);
|
||||
connector1.setName("connector1");
|
||||
|
||||
server.setConnectors(new Connector[]
|
||||
{ connector });
|
||||
{ connector0,connector1});
|
||||
|
||||
ContextHandler contextA = new ContextHandler("/");
|
||||
ContextHandler contextA = new ContextHandler("/ctx");
|
||||
contextA.setVirtualHosts(new String[]
|
||||
{ "www.example.com" });
|
||||
IsHandledHandler handlerA = new IsHandledHandler();
|
||||
{ "www.example.com", "alias.example.com" });
|
||||
IsHandledHandler handlerA = new IsHandledHandler("A");
|
||||
contextA.setHandler(handlerA);
|
||||
contextA.setAllowNullPathInfo(true);
|
||||
|
||||
ContextHandler contextB = new ContextHandler("/");
|
||||
IsHandledHandler handlerB = new IsHandledHandler();
|
||||
ContextHandler contextB = new ContextHandler("/ctx");
|
||||
IsHandledHandler handlerB = new IsHandledHandler("B");
|
||||
contextB.setHandler(handlerB);
|
||||
contextB.setVirtualHosts(new String[]
|
||||
{ "www.example2.com." });
|
||||
{ "*.other.com" , "@connector1"});
|
||||
|
||||
ContextHandler contextC = new ContextHandler("/");
|
||||
IsHandledHandler handlerC = new IsHandledHandler();
|
||||
ContextHandler contextC = new ContextHandler("/ctx");
|
||||
IsHandledHandler handlerC = new IsHandledHandler("C");
|
||||
contextC.setHandler(handlerC);
|
||||
|
||||
ContextHandlerCollection c = new ContextHandlerCollection();
|
||||
ContextHandler contextD = new ContextHandler("/");
|
||||
IsHandledHandler handlerD = new IsHandledHandler("D");
|
||||
contextD.setHandler(handlerD);
|
||||
|
||||
ContextHandler contextE = new ContextHandler("/ctx/foo");
|
||||
IsHandledHandler handlerE = new IsHandledHandler("E");
|
||||
contextE.setHandler(handlerE);
|
||||
|
||||
ContextHandler contextF = new ContextHandler("/ctxlong");
|
||||
IsHandledHandler handlerF = new IsHandledHandler("F");
|
||||
contextF.setHandler(handlerF);
|
||||
|
||||
ContextHandlerCollection c = new ContextHandlerCollection();
|
||||
c.addHandler(contextA);
|
||||
c.addHandler(contextB);
|
||||
c.addHandler(contextC);
|
||||
|
||||
|
||||
HandlerList list = new HandlerList();
|
||||
list.addHandler(contextD);
|
||||
list.addHandler(contextE);
|
||||
list.addHandler(contextF);
|
||||
c.addHandler(list);
|
||||
|
||||
server.setHandler(c);
|
||||
|
||||
try
|
||||
{
|
||||
server.start();
|
||||
connector.getResponses("GET / HTTP/1.0\n" + "Host: www.example.com.\n\n");
|
||||
|
||||
Object[][] tests = new Object[][] {
|
||||
{connector0,"www.example.com.", "/ctx", handlerA},
|
||||
{connector0,"www.example.com.", "/ctx/", handlerA},
|
||||
{connector0,"www.example.com.", "/ctx/info", handlerA},
|
||||
{connector0,"www.example.com", "/ctx/info", handlerA},
|
||||
{connector0,"alias.example.com", "/ctx/info", handlerA},
|
||||
{connector1,"www.example.com.", "/ctx/info", handlerA},
|
||||
{connector1,"www.example.com", "/ctx/info", handlerA},
|
||||
{connector1,"alias.example.com", "/ctx/info", handlerA},
|
||||
|
||||
assertTrue(handlerA.isHandled());
|
||||
assertFalse(handlerB.isHandled());
|
||||
assertFalse(handlerC.isHandled());
|
||||
{connector1,"www.other.com", "/ctx", null},
|
||||
{connector1,"www.other.com", "/ctx/", handlerB},
|
||||
{connector1,"www.other.com", "/ctx/info", handlerB},
|
||||
{connector0,"www.other.com", "/ctx/info", handlerC},
|
||||
|
||||
{connector0,"www.example.com", "/ctxinfo", handlerD},
|
||||
{connector1,"unknown.com", "/unknown", handlerD},
|
||||
|
||||
{connector0,"alias.example.com", "/ctx/foo/info", handlerE},
|
||||
{connector0,"alias.example.com", "/ctxlong/info", handlerF},
|
||||
};
|
||||
|
||||
for (int i=0;i<tests.length;i++)
|
||||
{
|
||||
Object[] test=tests[i];
|
||||
LocalConnector connector = (LocalConnector)test[0];
|
||||
String host=(String)test[1];
|
||||
String uri=(String)test[2];
|
||||
IsHandledHandler handler = (IsHandledHandler)test[3];
|
||||
|
||||
handlerA.reset();
|
||||
handlerB.reset();
|
||||
handlerC.reset();
|
||||
handlerA.reset();
|
||||
handlerB.reset();
|
||||
handlerC.reset();
|
||||
handlerD.reset();
|
||||
handlerE.reset();
|
||||
handlerF.reset();
|
||||
|
||||
connector.getResponses("GET / HTTP/1.0\n" + "Host: www.example2.com\n\n");
|
||||
|
||||
assertFalse(handlerA.isHandled());
|
||||
assertTrue(handlerB.isHandled());
|
||||
assertFalse(handlerC.isHandled());
|
||||
// System.err.printf("test %d %s@%s --> %s | %s%n",i,uri,host,connector.getName(),handler);
|
||||
String response = connector.getResponses("GET "+uri+" HTTP/1.0\nHost: "+host+"\n\n");
|
||||
|
||||
if (handler==null)
|
||||
{
|
||||
Assert.assertThat(response,Matchers.containsString(" 302 "));
|
||||
}
|
||||
else if (!handler.isHandled())
|
||||
{
|
||||
System.err.printf("FAILED %d %s@%s --> %s | %s%n",i,uri,host,connector.getName(),handler);
|
||||
System.err.println(response);
|
||||
Assert.fail();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
finally
|
||||
|
@ -103,7 +166,7 @@ public class ContextHandlerCollectionTest
|
|||
|
||||
ContextHandler context = new ContextHandler("/");
|
||||
|
||||
IsHandledHandler handler = new IsHandledHandler();
|
||||
IsHandledHandler handler = new IsHandledHandler("H");
|
||||
context.setHandler(handler);
|
||||
|
||||
ContextHandlerCollection c = new ContextHandlerCollection();
|
||||
|
@ -149,7 +212,10 @@ public class ContextHandlerCollectionTest
|
|||
|
||||
for(String host : requestHosts)
|
||||
{
|
||||
connector.getResponses("GET / HTTP/1.0\n" + "Host: "+host+"\nConnection:close\n\n");
|
||||
// System.err.printf("host=%s in %s%n",host,contextHosts==null?Collections.emptyList():Arrays.asList(contextHosts));
|
||||
|
||||
String response=connector.getResponses("GET / HTTP/1.0\n" + "Host: "+host+"\nConnection:close\n\n");
|
||||
// System.err.println(response);
|
||||
if(succeed)
|
||||
assertTrue("'"+host+"' should have been handled.",handler.isHandled());
|
||||
else
|
||||
|
@ -166,17 +232,17 @@ public class ContextHandlerCollectionTest
|
|||
Server server = new Server();
|
||||
|
||||
ContextHandler contextA = new ContextHandler("/a");
|
||||
IsHandledHandler handlerA = new IsHandledHandler();
|
||||
IsHandledHandler handlerA = new IsHandledHandler("A");
|
||||
contextA.setHandler(handlerA);
|
||||
|
||||
ContextHandler contextB = new ContextHandler("/b");
|
||||
IsHandledHandler handlerB = new IsHandledHandler();
|
||||
IsHandledHandler handlerB = new IsHandledHandler("B");
|
||||
HandlerWrapper wrapperB = new HandlerWrapper();
|
||||
wrapperB.setHandler(handlerB);
|
||||
contextB.setHandler(wrapperB);
|
||||
|
||||
ContextHandler contextC = new ContextHandler("/c");
|
||||
IsHandledHandler handlerC = new IsHandledHandler();
|
||||
IsHandledHandler handlerC = new IsHandledHandler("C");
|
||||
contextC.setHandler(handlerC);
|
||||
|
||||
ContextHandlerCollection collection = new ContextHandlerCollection();
|
||||
|
@ -202,22 +268,36 @@ public class ContextHandlerCollectionTest
|
|||
private static final class IsHandledHandler extends AbstractHandler
|
||||
{
|
||||
private boolean handled;
|
||||
private final String name;
|
||||
|
||||
public IsHandledHandler(String string)
|
||||
{
|
||||
name=string;
|
||||
}
|
||||
|
||||
public boolean isHandled()
|
||||
{
|
||||
return handled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(String s, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
this.handled = true;
|
||||
response.getWriter().print(name);
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
handled = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -118,12 +118,10 @@ public class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
|||
@Override
|
||||
public boolean put(String s, V v)
|
||||
{
|
||||
int last=EQ;
|
||||
int t=_tree[last];
|
||||
int k;
|
||||
int t=0;
|
||||
int limit = s.length();
|
||||
int node=0;
|
||||
for(k=0; k < limit; k++)
|
||||
int last=0;
|
||||
for(int k=0; k < limit; k++)
|
||||
{
|
||||
char c=s.charAt(k);
|
||||
if(isCaseInsensitive() && c<128)
|
||||
|
@ -131,49 +129,66 @@ public class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
|||
|
||||
while (true)
|
||||
{
|
||||
if (t==0)
|
||||
int row=ROW_SIZE*t;
|
||||
|
||||
// Do we need to create the new row?
|
||||
if (t==_rows)
|
||||
{
|
||||
node=t=++_rows;
|
||||
_rows++;
|
||||
if (_rows>=_key.length)
|
||||
{
|
||||
_rows--;
|
||||
return false;
|
||||
}
|
||||
int row=ROW_SIZE*t;
|
||||
_tree[row]=c;
|
||||
_tree[last]=(char)t;
|
||||
last=row+EQ;
|
||||
}
|
||||
|
||||
int row=ROW_SIZE*t;
|
||||
char n=_tree[row];
|
||||
int diff=n-c;
|
||||
if (diff==0)
|
||||
{
|
||||
node=t;
|
||||
t=_tree[last=(row+EQ)];
|
||||
break;
|
||||
}
|
||||
if (diff<0)
|
||||
else if (diff<0)
|
||||
t=_tree[last=(row+LO)];
|
||||
else
|
||||
t=_tree[last=(row+HI)];
|
||||
|
||||
// do we need a new row?
|
||||
if (t==0)
|
||||
{
|
||||
t=_rows;
|
||||
_tree[last]=(char)t;
|
||||
}
|
||||
|
||||
if (diff==0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
_key[node]=v==null?null:s;
|
||||
_value[node] = v;
|
||||
|
||||
|
||||
// Do we need to create the new row?
|
||||
if (t==_rows)
|
||||
{
|
||||
_rows++;
|
||||
if (_rows>=_key.length)
|
||||
{
|
||||
_rows--;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Put the key and value
|
||||
_key[t]=v==null?null:s;
|
||||
_value[t] = v;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public V get(String s,int offset, int length)
|
||||
public V get(String s,int offset, int len)
|
||||
{
|
||||
int t = _tree[EQ];
|
||||
int len = length;
|
||||
int i=0;
|
||||
while(i<len)
|
||||
int t = 0;
|
||||
for(int i=0; i < len;)
|
||||
{
|
||||
char c=s.charAt(offset+i++);
|
||||
if(isCaseInsensitive() && c<128)
|
||||
|
@ -187,8 +202,6 @@ public class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
|||
|
||||
if (diff==0)
|
||||
{
|
||||
if (i==len)
|
||||
return (V)_value[t];
|
||||
t=_tree[row+EQ];
|
||||
if (t==0)
|
||||
return null;
|
||||
|
@ -201,19 +214,17 @@ public class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
|||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return (V)_value[t];
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public V get(ByteBuffer b, int offset, int length)
|
||||
public V get(ByteBuffer b, int offset, int len)
|
||||
{
|
||||
int t = _tree[EQ];
|
||||
int len = length;
|
||||
int i=0;
|
||||
int t = 0;
|
||||
offset+=b.position();
|
||||
|
||||
while(i<len)
|
||||
for(int i=0; i < len;)
|
||||
{
|
||||
byte c=(byte)(b.get(offset+i++)&0x7f);
|
||||
if(isCaseInsensitive())
|
||||
|
@ -227,8 +238,6 @@ public class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
|||
|
||||
if (diff==0)
|
||||
{
|
||||
if (i==len)
|
||||
return (V)_value[t];
|
||||
t=_tree[row+EQ];
|
||||
if (t==0)
|
||||
return null;
|
||||
|
@ -240,35 +249,35 @@ public class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
return (V)_value[t];
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public V getBest(String s)
|
||||
{
|
||||
return getBest(_tree[EQ],s,0,s.length());
|
||||
return getBest(0,s,0,s.length());
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@Override
|
||||
public V getBest(String s, int offset, int length)
|
||||
{
|
||||
return getBest(_tree[EQ],s,offset,length);
|
||||
return getBest(0,s,offset,length);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
private V getBest(int t,String s,int offset,int len)
|
||||
{
|
||||
int node=0;
|
||||
for(int i=0; t!=0 && i<len; i++)
|
||||
int node=t;
|
||||
loop: for(int i=0; i<len; i++)
|
||||
{
|
||||
char c=s.charAt(offset+i);
|
||||
if(isCaseInsensitive() && c<128)
|
||||
c=StringUtil.lowercases[c];
|
||||
|
||||
while (t!=0)
|
||||
while (true)
|
||||
{
|
||||
int row = ROW_SIZE*t;
|
||||
char n=_tree[row];
|
||||
|
@ -276,25 +285,27 @@ public class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
|||
|
||||
if (diff==0)
|
||||
{
|
||||
node=t;
|
||||
t=_tree[row+EQ];
|
||||
if (t==0)
|
||||
break loop;
|
||||
|
||||
// if this node is a match, recurse to remember
|
||||
if (_key[node]!=null)
|
||||
if (_key[t]!=null)
|
||||
{
|
||||
node=t;
|
||||
V best=getBest(t,s,offset+i+1,len-i-1);
|
||||
if (best!=null)
|
||||
return best;
|
||||
return (V)_value[node];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
t=_tree[row+hilo(diff)];
|
||||
if (t==0)
|
||||
break loop;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return (V)_value[node];
|
||||
}
|
||||
|
||||
|
||||
|
@ -303,21 +314,21 @@ public class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
|||
public V getBest(ByteBuffer b, int offset, int len)
|
||||
{
|
||||
if (b.hasArray())
|
||||
return getBest(_tree[EQ],b.array(),b.arrayOffset()+b.position()+offset,len);
|
||||
return getBest(_tree[EQ],b,offset,len);
|
||||
return getBest(0,b.array(),b.arrayOffset()+b.position()+offset,len);
|
||||
return getBest(0,b,offset,len);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
private V getBest(int t,byte[] b, int offset, int len)
|
||||
{
|
||||
int node=0;
|
||||
for(int i=0; t!=0 && i<len; i++)
|
||||
int node=t;
|
||||
loop: for(int i=0; i<len; i++)
|
||||
{
|
||||
byte c=(byte)(b[offset+i]&0x7f);
|
||||
if(isCaseInsensitive())
|
||||
c=(byte)StringUtil.lowercases[c];
|
||||
|
||||
while (t!=0)
|
||||
while (true)
|
||||
{
|
||||
int row = ROW_SIZE*t;
|
||||
char n=_tree[row];
|
||||
|
@ -325,40 +336,42 @@ public class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
|||
|
||||
if (diff==0)
|
||||
{
|
||||
node=t;
|
||||
t=_tree[row+EQ];
|
||||
if (t==0)
|
||||
break loop;
|
||||
|
||||
// if this node is a match, recurse to remember
|
||||
if (_key[node]!=null)
|
||||
if (_key[t]!=null)
|
||||
{
|
||||
node=t;
|
||||
V best=getBest(t,b,offset+i+1,len-i-1);
|
||||
if (best!=null)
|
||||
return best;
|
||||
return (V)_value[node];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
t=_tree[row+hilo(diff)];
|
||||
if (t==0)
|
||||
break loop;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return (V)_value[node];
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
private V getBest(int t,ByteBuffer b, int offset, int len)
|
||||
{
|
||||
int node=0;
|
||||
int node=t;
|
||||
int o= offset+b.position();
|
||||
|
||||
for(int i=0; t!=0 && i<len; i++)
|
||||
loop: for(int i=0; i<len; i++)
|
||||
{
|
||||
byte c=(byte)(b.get(o+i)&0x7f);
|
||||
if(isCaseInsensitive())
|
||||
c=(byte)StringUtil.lowercases[c];
|
||||
|
||||
while (t!=0)
|
||||
while (true)
|
||||
{
|
||||
int row = ROW_SIZE*t;
|
||||
char n=_tree[row];
|
||||
|
@ -366,32 +379,34 @@ public class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
|||
|
||||
if (diff==0)
|
||||
{
|
||||
node=t;
|
||||
t=_tree[row+EQ];
|
||||
if (t==0)
|
||||
break loop;
|
||||
|
||||
// if this node is a match, recurse to remember
|
||||
if (_key[node]!=null)
|
||||
if (_key[t]!=null)
|
||||
{
|
||||
node=t;
|
||||
V best=getBest(t,b,offset+i+1,len-i-1);
|
||||
if (best!=null)
|
||||
return best;
|
||||
return (V)_value[node];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
t=_tree[row+hilo(diff)];
|
||||
if (t==0)
|
||||
break loop;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return (V)_value[node];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (int r=1;r<=_rows;r++)
|
||||
for (int r=0;r<=_rows;r++)
|
||||
{
|
||||
if (_key[r]!=null && _value[r]!=null)
|
||||
{
|
||||
|
@ -416,7 +431,7 @@ public class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
|||
{
|
||||
Set<String> keys = new HashSet<>();
|
||||
|
||||
for (int r=1;r<=_rows;r++)
|
||||
for (int r=0;r<=_rows;r++)
|
||||
{
|
||||
if (_key[r]!=null && _value[r]!=null)
|
||||
keys.add(_key[r]);
|
||||
|
@ -439,10 +454,10 @@ public class ArrayTernaryTrie<V> extends AbstractTrie<V>
|
|||
|
||||
public void dump()
|
||||
{
|
||||
for (int r=0;r<=_rows;r++)
|
||||
for (int r=0;r<_rows;r++)
|
||||
{
|
||||
char c=_tree[r*ROW_SIZE+0];
|
||||
System.err.printf("%4d [%s,%d,%d,%d] %s:%s%n",
|
||||
System.err.printf("%4d [%s,%d,%d,%d] '%s':%s%n",
|
||||
r,
|
||||
(c<' '||c>127)?(""+(int)c):"'"+c+"'",
|
||||
(int)_tree[r*ROW_SIZE+LO],
|
||||
|
|
|
@ -249,18 +249,20 @@ public class ArrayTrie<V> extends AbstractTrie<V>
|
|||
if (index>=0)
|
||||
{
|
||||
int idx=t*ROW_SIZE+index;
|
||||
t=_rowIndex[idx];
|
||||
if (t==0)
|
||||
return null;
|
||||
int nt=_rowIndex[idx];
|
||||
if (nt==0)
|
||||
break;
|
||||
t=nt;
|
||||
}
|
||||
else
|
||||
{
|
||||
char[] big = _bigIndex==null?null:_bigIndex[t];
|
||||
if (big==null)
|
||||
return null;
|
||||
t=big[c];
|
||||
if (t==0)
|
||||
return null;
|
||||
int nt=big[c];
|
||||
if (nt==0)
|
||||
break;
|
||||
t=nt;
|
||||
}
|
||||
|
||||
// Is the next Trie is a match
|
||||
|
@ -286,18 +288,20 @@ public class ArrayTrie<V> extends AbstractTrie<V>
|
|||
if (index>=0)
|
||||
{
|
||||
int idx=t*ROW_SIZE+index;
|
||||
t=_rowIndex[idx];
|
||||
if (t==0)
|
||||
return null;
|
||||
int nt=_rowIndex[idx];
|
||||
if (nt==0)
|
||||
break;
|
||||
t=nt;
|
||||
}
|
||||
else
|
||||
{
|
||||
char[] big = _bigIndex==null?null:_bigIndex[t];
|
||||
if (big==null)
|
||||
return null;
|
||||
t=big[c];
|
||||
if (t==0)
|
||||
return null;
|
||||
int nt=big[c];
|
||||
if (nt==0)
|
||||
break;
|
||||
t=nt;
|
||||
}
|
||||
|
||||
// Is the next Trie is a match
|
||||
|
@ -307,7 +311,7 @@ public class ArrayTrie<V> extends AbstractTrie<V>
|
|||
V best=getBest(t,b,offset+i+1,len-i-1);
|
||||
if (best!=null)
|
||||
return best;
|
||||
return (V)_value[t];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (V)_value[t];
|
||||
|
@ -323,18 +327,20 @@ public class ArrayTrie<V> extends AbstractTrie<V>
|
|||
if (index>=0)
|
||||
{
|
||||
int idx=t*ROW_SIZE+index;
|
||||
t=_rowIndex[idx];
|
||||
if (t==0)
|
||||
return null;
|
||||
int nt=_rowIndex[idx];
|
||||
if (nt==0)
|
||||
break;
|
||||
t=nt;
|
||||
}
|
||||
else
|
||||
{
|
||||
char[] big = _bigIndex==null?null:_bigIndex[t];
|
||||
if (big==null)
|
||||
return null;
|
||||
t=big[c];
|
||||
if (t==0)
|
||||
return null;
|
||||
int nt=big[c];
|
||||
if (nt==0)
|
||||
break;
|
||||
t=nt;
|
||||
}
|
||||
|
||||
// Is the next Trie is a match
|
||||
|
@ -344,7 +350,7 @@ public class ArrayTrie<V> extends AbstractTrie<V>
|
|||
V best=getBest(t,b,offset+i+1,len-i-1);
|
||||
if (best!=null)
|
||||
return best;
|
||||
return (V)_value[t];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (V)_value[t];
|
||||
|
|
|
@ -70,9 +70,8 @@ public class TreeTrie<V> extends AbstractTrie<V>
|
|||
public boolean put(String s, V v)
|
||||
{
|
||||
TreeTrie<V> t = this;
|
||||
int k;
|
||||
int limit = s.length();
|
||||
for(k=0; k < limit; k++)
|
||||
for(int k=0; k < limit; k++)
|
||||
{
|
||||
char c=s.charAt(k);
|
||||
|
||||
|
@ -102,7 +101,6 @@ public class TreeTrie<V> extends AbstractTrie<V>
|
|||
}
|
||||
}
|
||||
t._key=v==null?null:s;
|
||||
V old=t._value;
|
||||
t._value = v;
|
||||
return true;
|
||||
}
|
||||
|
@ -182,7 +180,7 @@ public class TreeTrie<V> extends AbstractTrie<V>
|
|||
if (index>=0)
|
||||
{
|
||||
if (t._nextIndex[index] == null)
|
||||
return null;
|
||||
break;
|
||||
t = t._nextIndex[index];
|
||||
}
|
||||
else
|
||||
|
@ -196,7 +194,7 @@ public class TreeTrie<V> extends AbstractTrie<V>
|
|||
n=null;
|
||||
}
|
||||
if (n==null)
|
||||
return null;
|
||||
break;
|
||||
t=n;
|
||||
}
|
||||
|
||||
|
@ -207,7 +205,7 @@ public class TreeTrie<V> extends AbstractTrie<V>
|
|||
V best=t.getBest(b,offset+i+1,len-i-1);
|
||||
if (best!=null)
|
||||
return best;
|
||||
return t._value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return t._value;
|
||||
|
@ -240,7 +238,7 @@ public class TreeTrie<V> extends AbstractTrie<V>
|
|||
if (index>=0)
|
||||
{
|
||||
if (t._nextIndex[index] == null)
|
||||
return null;
|
||||
break;
|
||||
t = t._nextIndex[index];
|
||||
}
|
||||
else
|
||||
|
@ -254,7 +252,7 @@ public class TreeTrie<V> extends AbstractTrie<V>
|
|||
n=null;
|
||||
}
|
||||
if (n==null)
|
||||
return null;
|
||||
break;
|
||||
t=n;
|
||||
}
|
||||
|
||||
|
@ -265,7 +263,7 @@ public class TreeTrie<V> extends AbstractTrie<V>
|
|||
V best=t.getBest(b,offset+i+1,len-i-1);
|
||||
if (best!=null)
|
||||
return best;
|
||||
return t._value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return t._value;
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.nio.ByteBuffer;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
@ -62,6 +63,21 @@ public class TrieTest
|
|||
trie.put("foo-bar",6);
|
||||
trie.put("foo+bar",7);
|
||||
trie.put("HELL4",8);
|
||||
trie.put("",9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeySet() throws Exception
|
||||
{
|
||||
Assert.assertTrue(trie.keySet().contains("hello"));
|
||||
Assert.assertTrue(trie.keySet().contains("He"));
|
||||
Assert.assertTrue(trie.keySet().contains("HELL"));
|
||||
Assert.assertTrue(trie.keySet().contains("wibble"));
|
||||
Assert.assertTrue(trie.keySet().contains("Wobble"));
|
||||
Assert.assertTrue(trie.keySet().contains("foo-bar"));
|
||||
Assert.assertTrue(trie.keySet().contains("foo+bar"));
|
||||
Assert.assertTrue(trie.keySet().contains("HELL4"));
|
||||
Assert.assertTrue(trie.keySet().contains(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -82,6 +98,8 @@ public class TrieTest
|
|||
Assert.assertEquals(5,trie.get("wobble").intValue());
|
||||
Assert.assertEquals(6,trie.get("Foo-bar").intValue());
|
||||
Assert.assertEquals(7,trie.get("FOO+bar").intValue());
|
||||
Assert.assertEquals(8,trie.get("HELL4").intValue());
|
||||
Assert.assertEquals(9,trie.get("").intValue());
|
||||
|
||||
Assert.assertEquals(null,trie.get("helloworld"));
|
||||
Assert.assertEquals(null,trie.get("Help"));
|
||||
|
@ -151,6 +169,7 @@ public class TrieTest
|
|||
Assert.assertEquals(3,trie.getBest(StringUtil.getUtf8Bytes("xHELLxxxxx"),1,8).intValue());
|
||||
Assert.assertEquals(6,trie.getBest(StringUtil.getUtf8Bytes("xfoo-BARxx"),1,8).intValue());
|
||||
Assert.assertEquals(8,trie.getBest(StringUtil.getUtf8Bytes("xHELL4xxxx"),1,8).intValue());
|
||||
Assert.assertEquals(9,trie.getBest(StringUtil.getUtf8Bytes("xZZZZZxxxx"),1,8).intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -167,6 +186,7 @@ public class TrieTest
|
|||
Assert.assertEquals(3,trie.getBest(BufferUtil.toBuffer("xHELLxxxxx"),1,8).intValue());
|
||||
Assert.assertEquals(6,trie.getBest(BufferUtil.toBuffer("xfoo-BARxx"),1,8).intValue());
|
||||
Assert.assertEquals(8,trie.getBest(BufferUtil.toBuffer("xHELL4xxxx"),1,8).intValue());
|
||||
Assert.assertEquals(9,trie.getBest(BufferUtil.toBuffer("xZZZZZxxxx"),1,8).intValue());
|
||||
|
||||
ByteBuffer buffer = (ByteBuffer)BufferUtil.toBuffer("xhelloxxxxxxx").position(2);
|
||||
Assert.assertEquals(1,trie.getBest(buffer,-1,10).intValue());
|
||||
|
@ -186,6 +206,7 @@ public class TrieTest
|
|||
Assert.assertEquals(3,trie.getBest(BufferUtil.toDirectBuffer("xHELLxxxxx"),1,8).intValue());
|
||||
Assert.assertEquals(6,trie.getBest(BufferUtil.toDirectBuffer("xfoo-BARxx"),1,8).intValue());
|
||||
Assert.assertEquals(8,trie.getBest(BufferUtil.toDirectBuffer("xHELL4xxxx"),1,8).intValue());
|
||||
Assert.assertEquals(9,trie.getBest(BufferUtil.toDirectBuffer("xZZZZZxxxx"),1,8).intValue());
|
||||
|
||||
ByteBuffer buffer = (ByteBuffer)BufferUtil.toDirectBuffer("xhelloxxxxxxx").position(2);
|
||||
Assert.assertEquals(1,trie.getBest(buffer,-1,10).intValue());
|
||||
|
|
|
@ -57,12 +57,12 @@ public abstract class AbstractNewSessionTest
|
|||
@Test
|
||||
public void testNewSession() throws Exception
|
||||
{
|
||||
String contextPath = "";
|
||||
String servletMapping = "/server";
|
||||
int scavengePeriod = 3;
|
||||
AbstractTestServer server = createServer(0, 1, scavengePeriod);
|
||||
ServletContextHandler context = server.addContext(contextPath);
|
||||
ServletContextHandler context = server.addContext("/");
|
||||
context.addServlet(TestServlet.class, servletMapping);
|
||||
String contextPath = "";
|
||||
|
||||
try
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue