Merge remote-tracking branch 'origin/master' into jetty-7.6

Conflicts:
	example-jetty-embedded/pom.xml
	jetty-aggregate/jetty-all-server/pom.xml
	jetty-aggregate/jetty-all/pom.xml
	jetty-aggregate/jetty-client/pom.xml
	jetty-aggregate/jetty-plus/pom.xml
	jetty-aggregate/jetty-server/pom.xml
	jetty-aggregate/jetty-servlet/pom.xml
	jetty-aggregate/jetty-webapp/pom.xml
	jetty-aggregate/pom.xml
	jetty-ajp/pom.xml
	jetty-annotations/pom.xml
	jetty-client/pom.xml
	jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java
	jetty-client/src/main/java/org/eclipse/jetty/client/SelectConnector.java
	jetty-continuation/pom.xml
	jetty-deploy/pom.xml
	jetty-distribution/pom.xml
	jetty-http-spi/pom.xml
	jetty-http/pom.xml
	jetty-http/src/main/java/org/eclipse/jetty/http/ssl/SslContextFactory.java
	jetty-io/pom.xml
	jetty-io/src/main/java/org/eclipse/jetty/io/nio/ChannelEndPoint.java
	jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java
	jetty-jaspi/pom.xml
	jetty-jmx/pom.xml
	jetty-jndi/pom.xml
	jetty-jsp-2.1/pom.xml
	jetty-monitor/pom.xml
	jetty-nested/pom.xml
	jetty-nosql/pom.xml
	jetty-osgi/jetty-osgi-boot-jsp/pom.xml
	jetty-osgi/jetty-osgi-boot-logback/pom.xml
	jetty-osgi/jetty-osgi-boot-warurl/pom.xml
	jetty-osgi/jetty-osgi-boot/pom.xml
	jetty-osgi/jetty-osgi-equinoxtools/pom.xml
	jetty-osgi/jetty-osgi-httpservice/pom.xml
	jetty-osgi/pom.xml
	jetty-osgi/test-jetty-osgi/pom.xml
	jetty-overlay-deployer/pom.xml
	jetty-plus/pom.xml
	jetty-policy/pom.xml
	jetty-rewrite/pom.xml
	jetty-security/pom.xml
	jetty-server/pom.xml
	jetty-servlet/pom.xml
	jetty-servlets/pom.xml
	jetty-start/pom.xml
	jetty-util/pom.xml
	jetty-util/src/test/java/org/eclipse/jetty/util/log/LogTest.java
	jetty-util/src/test/java/org/eclipse/jetty/util/log/NamedLogTest.java
	jetty-webapp/pom.xml
	jetty-websocket/pom.xml
	jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD13.java
	jetty-xml/pom.xml
	pom.xml
	test-continuation-jetty6/pom.xml
	test-continuation/pom.xml
	test-jetty-nested/pom.xml
	test-jetty-servlet/pom.xml
	test-jetty-webapp/pom.xml
	tests/pom.xml
	tests/test-integration/pom.xml
	tests/test-loginservice/pom.xml
	tests/test-sessions/pom.xml
	tests/test-sessions/test-hash-sessions/pom.xml
	tests/test-sessions/test-jdbc-sessions/pom.xml
	tests/test-sessions/test-sessions-common/pom.xml
	tests/test-webapps/pom.xml
	tests/test-webapps/test-webapp-rfc2616/pom.xml
This commit is contained in:
Greg Wilkins 2011-11-07 18:09:31 +11:00
commit 7a082280f1
42 changed files with 1728 additions and 490 deletions

5
.gitignore vendored
View File

@ -20,7 +20,7 @@ target/
.idea/ .idea/
# Mac filesystem dust # Mac filesystem dust
/.DS_Store .DS_Store
# pmd # pmd
.pmdruleset .pmdruleset
@ -31,3 +31,6 @@ target/
# vim # vim
.*.sw[a-p] .*.sw[a-p]
# merge tooling
*.orig

View File

@ -1,4 +1,19 @@
jetty-7.5.4-SNAPSHOT jetty-7.5.5-SNAPSHOT
jetty-7.5.4.v20111024 - 24 October 2011
+ 358263 JDBCSessionIdManager add setDatasource(DataSource) method
+ 358649 Replace existing StdErrLog system properties for DEBUG/IGNORED with
LEVEL instead.
+ 360836 Accept parameters with bad UTF-8. Use replacement character
+ 360912 CrossOriginFilter does not send Access-Control-Allow-Origin on
responses. 355103 Make allowCredentials default to true in
CrossOriginFilter.
+ 360938 Connections closed after a while.
+ 361319 Log initialization does not catch correct exceptions on all jvms
+ 361325 359292 Allow KeyStore to be set
+ 361456 release timer task on connection failed
+ 361655 ExecutorThreadPool.isLowOnThreads() returns wrong value.
+ JETTY-1444 start threadpool before selector manager
jetty-7.5.3.v20111011 - 11 October 2011 jetty-7.5.3.v20111011 - 11 October 2011
+ 348978 migrate jetty-http-spi + 348978 migrate jetty-http-spi

View File

@ -15,7 +15,7 @@ package org.eclipse.jetty.client;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.net.ConnectException; import java.net.ProtocolException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -65,7 +65,7 @@ public class HttpDestination implements Dumpable
private List<HttpCookie> _cookies; private List<HttpCookie> _cookies;
HttpDestination(HttpClient client, Address address, boolean ssl) HttpDestination(HttpClient client, Address address, boolean ssl)
{ {
_client = client; _client = client;
@ -528,7 +528,7 @@ public class HttpDestination implements Dumpable
// Add any known authorizations // Add any known authorizations
if (_authorizations != null) if (_authorizations != null)
{ {
Authentication auth = (Authentication)_authorizations.match(ex.getURI()); Authentication auth = (Authentication)_authorizations.match(ex.getRequestURI());
if (auth != null) if (auth != null)
(auth).setCredentials(ex); (auth).setCredentials(ex);
} }
@ -669,7 +669,7 @@ public class HttpDestination implements Dumpable
AggregateLifeCycle.dump(out,indent,_connections); AggregateLifeCycle.dump(out,indent,_connections);
} }
} }
private class ConnectExchange extends ContentExchange private class ConnectExchange extends ContentExchange
{ {
private final SelectConnector.UpgradableEndPoint proxyEndPoint; private final SelectConnector.UpgradableEndPoint proxyEndPoint;
@ -691,13 +691,18 @@ public class HttpDestination implements Dumpable
@Override @Override
protected void onResponseComplete() throws IOException protected void onResponseComplete() throws IOException
{ {
if (getResponseStatus() == HttpStatus.OK_200) int responseStatus = getResponseStatus();
if (responseStatus == HttpStatus.OK_200)
{ {
proxyEndPoint.upgrade(); proxyEndPoint.upgrade();
} }
else if(responseStatus == HttpStatus.GATEWAY_TIMEOUT_504)
{
onExpire();
}
else else
{ {
onConnectionFailed(new ConnectException(exchange.getAddress().toString())); onException(new ProtocolException("Proxy: " + proxyEndPoint.getRemoteAddr() +":" + proxyEndPoint.getRemotePort() + " didn't return http return code 200, but " + responseStatus + " while trying to request: " + exchange.getAddress().toString()));
} }
} }
@ -706,5 +711,22 @@ public class HttpDestination implements Dumpable
{ {
HttpDestination.this.onConnectionFailed(x); HttpDestination.this.onConnectionFailed(x);
} }
@Override
protected void onException(Throwable x)
{
_queue.remove(exchange);
exchange.setStatus(STATUS_EXCEPTED);
exchange.getEventListener().onException(x);
}
@Override
protected void onExpire()
{
_queue.remove(exchange);
exchange.setStatus(STATUS_EXPIRED);
exchange.getEventListener().onExpire();
}
} }
} }

View File

@ -0,0 +1,126 @@
// ========================================================================
// Copyright (c) 2009-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package org.eclipse.jetty.client;
import java.io.IOException;
import java.net.ProtocolException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.HttpConnection;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ConnectHandler;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/* ------------------------------------------------------------ */
/**
* This UnitTest class executes two tests. Both will send a http request to https://google.com through a misbehaving proxy server.
* <p/>
* The first test runs against a proxy which simply closes the connection (as nginx does) for a connect request. The second proxy server always responds with a
* 500 error.
* <p/>
* The expected result for both tests is an exception and the HttpExchange should have status HttpExchange.STATUS_EXCEPTED.
*/
public class HttpsViaBrokenHttpProxyTest
{
private Server _proxy = new Server();
private HttpClient _client = new HttpClient();
@Before
public void init() throws Exception
{
// setup proxies with different behaviour
_proxy.addConnector(new SelectChannelConnector());
_proxy.setHandler(new BadBehavingConnectHandler());
_proxy.start();
int proxyClosingConnectionPort = _proxy.getConnectors()[0].getLocalPort();
_client.setProxy(new Address("localhost", proxyClosingConnectionPort));
_client.start();
}
@After
public void destroy() throws Exception
{
_client.stop();
_proxy.stop();
}
@Test
public void httpsViaProxyThatClosesConnectionOnConnectRequestTest() throws Exception
{
sendRequestThroughProxy(new ContentExchange(), "close", 9);
}
@Test
public void httpsViaProxyThatReturns500ErrorTest() throws Exception
{
HttpExchange exchange = new ContentExchange()
{
@Override
protected void onException(Throwable x)
{
// Suppress logging for expected exception
if (!(x instanceof ProtocolException))
super.onException(x);
}
};
sendRequestThroughProxy(exchange, "error500", 9);
}
@Test
public void httpsViaProxyThatReturns504ErrorTest() throws Exception
{
sendRequestThroughProxy(new ContentExchange(), "error504", 8);
}
private void sendRequestThroughProxy(HttpExchange exchange, String desiredBehaviour, int exptectedStatus) throws Exception
{
String url = "https://" + desiredBehaviour + ".com/";
exchange.setURL(url);
exchange.addRequestHeader("behaviour", desiredBehaviour);
_client.send(exchange);
assertEquals(HttpExchange.toState(exptectedStatus) + " status awaited", exptectedStatus, exchange.waitForDone());
}
private class BadBehavingConnectHandler extends ConnectHandler
{
@Override
protected void handleConnect(Request baseRequest, HttpServletRequest request, HttpServletResponse response, String serverAddress)
throws ServletException, IOException
{
if (serverAddress.contains("close"))
{
HttpConnection.getCurrentConnection().getEndPoint().close();
}
else if (serverAddress.contains("error500"))
{
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
}
else if (serverAddress.contains("error504"))
{
response.setStatus(HttpStatus.GATEWAY_TIMEOUT_504);
}
baseRequest.setHandled(true);
}
}
}

View File

@ -42,7 +42,7 @@ public class ProxyTunnellingTest
{ {
return proxyConnector.getLocalPort(); return proxyConnector.getLocalPort();
} }
protected void startSSLServer(Handler handler) throws Exception protected void startSSLServer(Handler handler) throws Exception
{ {
SslSelectChannelConnector connector = new SslSelectChannelConnector(); SslSelectChannelConnector connector = new SslSelectChannelConnector();
@ -218,11 +218,11 @@ public class ProxyTunnellingTest
ContentExchange exchange = new ContentExchange(true) ContentExchange exchange = new ContentExchange(true)
{ {
@Override @Override
protected void onConnectionFailed(Throwable x) protected void onException(Throwable x)
{ {
latch.countDown(); latch.countDown();
} }
}; };
exchange.setMethod(HttpMethods.GET); exchange.setMethod(HttpMethods.GET);
String body = "BODY"; String body = "BODY";

View File

@ -69,4 +69,4 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</project> </project>

View File

@ -21,5 +21,4 @@ public class SslContextFactory extends org.eclipse.jetty.util.ssl.SslContextFact
{ {
super(keyStorePath); super(keyStorePath);
} }
} }

View File

@ -6,6 +6,7 @@ import java.io.FileInputStream;
import java.security.KeyStore; import java.security.KeyStore;
import org.eclipse.jetty.http.ssl.SslContextFactory; import org.eclipse.jetty.http.ssl.SslContextFactory;
import org.eclipse.jetty.util.resource.Resource;
import org.junit.Test; import org.junit.Test;
@ -72,4 +73,70 @@ public class SslContextFactoryTest
cf.start(); cf.start();
assertTrue(cf.getSslContext()!=null); assertTrue(cf.getSslContext()!=null);
} }
@Test
public void testNoTsResourceKs() throws Exception
{
Resource keystoreResource = Resource.newSystemResource("keystore");
SslContextFactory cf = new SslContextFactory();
cf.setKeyStoreResource(keystoreResource);
cf.setKeyStorePassword("storepwd");
cf.setKeyManagerPassword("keypwd");
cf.start();
assertTrue(cf.getSslContext()!=null);
}
@Test
public void testResourceTsResourceKs() throws Exception
{
Resource keystoreResource = Resource.newSystemResource("keystore");
Resource truststoreResource = Resource.newSystemResource("keystore");
SslContextFactory cf = new SslContextFactory();
cf.setKeyStoreResource(keystoreResource);
cf.setTrustStoreResource(truststoreResource);
cf.setKeyStorePassword("storepwd");
cf.setKeyManagerPassword("keypwd");
cf.setTrustStorePassword("storepwd");
cf.start();
assertTrue(cf.getSslContext()!=null);
}
@Test(expected = java.security.UnrecoverableKeyException.class)
public void testResourceTsResourceKsWrongPW() throws Exception
{
Resource keystoreResource = Resource.newSystemResource("keystore");
Resource truststoreResource = Resource.newSystemResource("keystore");
SslContextFactory cf = new SslContextFactory();
cf.setKeyStoreResource(keystoreResource);
cf.setTrustStoreResource(truststoreResource);
cf.setKeyStorePassword("storepwd");
cf.setKeyManagerPassword("wrong_keypwd");
cf.setTrustStorePassword("storepwd");
cf.start();
}
@Test(expected = java.io.IOException.class)
public void testResourceTsWrongPWResourceKs() throws Exception
{
Resource keystoreResource = Resource.newSystemResource("keystore");
Resource truststoreResource = Resource.newSystemResource("keystore");
SslContextFactory cf = new SslContextFactory();
cf.setKeyStoreResource(keystoreResource);
cf.setTrustStoreResource(truststoreResource);
cf.setKeyStorePassword("storepwd");
cf.setKeyManagerPassword("keypwd");
cf.setTrustStorePassword("wrong_storepwd");
cf.start();
}
} }

View File

@ -195,7 +195,7 @@ public class ChannelEndPoint implements EndPoint
{ {
final NIOBuffer nbuf = (NIOBuffer)buf; final NIOBuffer nbuf = (NIOBuffer)buf;
final ByteBuffer bbuf=nbuf.getByteBuffer(); final ByteBuffer bbuf=nbuf.getByteBuffer();
//noinspection SynchronizationOnLocalVariableOrMethodParameter //noinspection SynchronizationOnLocalVariableOrMethodParameter
try try
{ {
@ -234,7 +234,7 @@ public class ChannelEndPoint implements EndPoint
{ {
LOG.ignore(xx); LOG.ignore(xx);
} }
if (len>0) if (len>0)
throw x; throw x;
len=-1; len=-1;
@ -244,7 +244,7 @@ public class ChannelEndPoint implements EndPoint
{ {
throw new IOException("Not Implemented"); throw new IOException("Not Implemented");
} }
return len; return len;
} }

View File

@ -64,7 +64,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
private long _lowResourcesConnections; private long _lowResourcesConnections;
private SelectSet[] _selectSet; private SelectSet[] _selectSet;
private int _selectSets=1; private int _selectSets=1;
private volatile int _set; private volatile int _set=0;
private boolean _deferringInterestedOps0=true; private boolean _deferringInterestedOps0=true;
private int _selectorPriorityDelta=0; private int _selectorPriorityDelta=0;
@ -129,6 +129,8 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
// be distributed over the available sets. // be distributed over the available sets.
int s=_set++; int s=_set++;
if (s<0)
s=-s;
s=s%_selectSets; s=s%_selectSets;
SelectSet[] sets=_selectSet; SelectSet[] sets=_selectSet;
if (sets!=null) if (sets!=null)
@ -151,6 +153,8 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
// be distributed over the available sets. // be distributed over the available sets.
int s=_set++; int s=_set++;
if (s<0)
s=-s;
s=s%_selectSets; s=s%_selectSets;
SelectSet[] sets=_selectSet; SelectSet[] sets=_selectSet;
if (sets!=null) if (sets!=null)
@ -168,6 +172,8 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
public void register(ServerSocketChannel acceptChannel) public void register(ServerSocketChannel acceptChannel)
{ {
int s=_set++; int s=_set++;
if (s<0)
s=-s;
s=s%_selectSets; s=s%_selectSets;
SelectSet set=_selectSet[s]; SelectSet set=_selectSet[s];
set.addChange(acceptChannel); set.addChange(acceptChannel);

View File

@ -483,5 +483,15 @@ public class ProxyRule extends PatternRule
{ {
_connectorType = connectorType; _connectorType = connectorType;
} }
public String getHostHeader()
{
return _hostHeader;
}
public void setHostHeader(String hostHeader)
{
_hostHeader = hostHeader;
}
} }

View File

@ -1,5 +1,5 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.eclipse.org/configure.dtd"> <!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
<!-- =============================================================== --> <!-- =============================================================== -->
<!-- Configure the Jetty Server --> <!-- Configure the Jetty Server -->
@ -85,7 +85,7 @@
<Array type="org.eclipse.jetty.rewrite.handler.Rule"> <Array type="org.eclipse.jetty.rewrite.handler.Rule">
<Item> <Item>
<New id="" class="org.eclipse.jetty.rewrite.handler.RewritePatternRule"> <New id="rewrite" class="org.eclipse.jetty.rewrite.handler.RewritePatternRule">
<Set name="pattern">/*</Set> <Set name="pattern">/*</Set>
<Set name="replacement">/test</Set> <Set name="replacement">/test</Set>
</New> </New>

View File

@ -86,7 +86,18 @@ public class DigestAuthenticator extends LoginAuthenticator
String mna=configuration.getInitParameter("maxNonceAge"); String mna=configuration.getInitParameter("maxNonceAge");
if (mna!=null) if (mna!=null)
_maxNonceAgeMs=Long.valueOf(mna); {
synchronized (this)
{
_maxNonceAgeMs=Long.valueOf(mna);
}
}
}
/* ------------------------------------------------------------ */
public synchronized void setMaxNonceAge(long maxNonceAgeInMillis)
{
_maxNonceAgeMs = maxNonceAgeInMillis;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -234,7 +245,11 @@ public class DigestAuthenticator extends LoginAuthenticator
private int checkNonce(Digest digest, Request request) private int checkNonce(Digest digest, Request request)
{ {
// firstly let's expire old nonces // firstly let's expire old nonces
long expired = request.getTimeStamp()-_maxNonceAgeMs; long expired;
synchronized (this)
{
expired = request.getTimeStamp()-_maxNonceAgeMs;
}
Nonce nonce=_nonceQueue.peek(); Nonce nonce=_nonceQueue.peek();
while (nonce!=null && nonce._ts<expired) while (nonce!=null && nonce._ts<expired)

View File

@ -263,6 +263,85 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
} }
} }
/* ------------------------------------------------------------ */
/** Either set virtual hosts or add to an existing set of virtual hosts.
*
* @param virtualHosts
* Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
* String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
*/
public void addVirtualHosts(String[] virtualHosts)
{
if (virtualHosts == null) // since this is add, we don't null the old ones
{
return;
}
else
{
List<String> currentVirtualHosts = null;
if (_vhosts != null)
{
currentVirtualHosts = new ArrayList<String>(Arrays.asList(_vhosts));
}
else
{
currentVirtualHosts = new ArrayList<String>();
}
for (int i = 0; i < virtualHosts.length; i++)
{
String normVhost = normalizeHostname(virtualHosts[i]);
if (!currentVirtualHosts.contains(normVhost))
{
currentVirtualHosts.add(normVhost);
}
}
_vhosts = currentVirtualHosts.toArray(new String[0]);
}
}
/* ------------------------------------------------------------ */
/**
* Removes an array of virtual host entries, if this removes all entries the _vhosts will be set to null
*
* @param virtualHosts
* Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
* String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
*/
public void removeVirtualHosts(String[] virtualHosts)
{
if (virtualHosts == null)
{
return; // do nothing
}
else if ( _vhosts == null || _vhosts.length == 0)
{
return; // do nothing
}
else
{
List<String> existingVirtualHosts = new ArrayList<String>(Arrays.asList(_vhosts));
for (int i = 0; i < virtualHosts.length; i++)
{
String toRemoveVirtualHost = normalizeHostname(virtualHosts[i]);
if (existingVirtualHosts.contains(toRemoveVirtualHost))
{
existingVirtualHosts.remove(toRemoveVirtualHost);
}
}
if (existingVirtualHosts.isEmpty())
{
_vhosts = null; // if we ended up removing them all, just null out _vhosts
}
else
{
_vhosts = existingVirtualHosts.toArray(new String[0]);
}
}
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* Get the virtual hosts for the context. Only requests that have a matching host header or fully qualified URL will be passed to that context with a * Get the virtual hosts for the context. Only requests that have a matching host header or fully qualified URL will be passed to that context with a

View File

@ -93,7 +93,7 @@ public class SelectChannelConnector extends AbstractNIOConnector
public void accept(int acceptorID) throws IOException public void accept(int acceptorID) throws IOException
{ {
ServerSocketChannel server = _acceptChannel; ServerSocketChannel server = _acceptChannel;
if (server!=null && server.isOpen()) if (server!=null && server.isOpen() && _manager.isStarted())
{ {
SocketChannel channel = _acceptChannel.accept(); SocketChannel channel = _acceptChannel.accept();
channel.configureBlocking(false); channel.configureBlocking(false);

View File

@ -232,6 +232,37 @@ public class ContextHandlerTest
} }
} }
@Test
public void testVirtualHostManagement() throws Exception
{
ContextHandler context = new ContextHandler("/");
// test singular
context.setVirtualHosts(new String[] { "www.example.com"} );
Assert.assertEquals(1,context.getVirtualHosts().length);
// test adding two more
context.addVirtualHosts(new String[] { "www.example2.com", "www.example3.com"});
Assert.assertEquals(3,context.getVirtualHosts().length);
// test adding existing context
context.addVirtualHosts(new String[] { "www.example.com" });
Assert.assertEquals(3,context.getVirtualHosts().length);
// test removing existing
context.removeVirtualHosts(new String[] { "www.example3.com" });
Assert.assertEquals(2,context.getVirtualHosts().length);
// test removing non-existent
context.removeVirtualHosts(new String[] { "www.example3.com" });
Assert.assertEquals(2,context.getVirtualHosts().length);
// test removing all remaining and resets to null
context.removeVirtualHosts(new String[] { "www.example.com", "www.example2.com" });
Assert.assertEquals(null,context.getVirtualHosts());
}
@Test @Test
public void testAttributes() throws Exception public void testAttributes() throws Exception
{ {

View File

@ -4,11 +4,11 @@
// All rights reserved. This program and the accompanying materials // All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0 // are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution. // and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at // The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html // http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at // The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php // http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses. // You may elect to redistribute this code under either of these licenses.
// ======================================================================== // ========================================================================
package org.eclipse.jetty.servlets; package org.eclipse.jetty.servlets;
@ -34,27 +34,26 @@ import org.eclipse.jetty.util.log.Logger;
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
/** /**
* CGI Servlet. * CGI Servlet.
* * <p/>
* The cgi bin directory can be set with the "cgibinResourceBase" init parameter * The cgi bin directory can be set with the "cgibinResourceBase" init parameter or it will default to the resource base of the context. If the
* or it will default to the resource base of the context. * "cgibinResourceBaseIsRelative" init parameter is set the resource base is relative to the webapp. For example "WEB-INF/cgi" would work.
* * <br/>
* The "commandPrefix" init parameter may be used to set a prefix to all * Not that this only works for extracted war files as "jar cf" will not reserve the execute permissions on the cgi files.
* commands passed to exec. This can be used on systems that need assistance to * <p/>
* execute a particular file type. For example on windows this can be set to * The "commandPrefix" init parameter may be used to set a prefix to all commands passed to exec. This can be used on systems that need assistance to execute a
* "perl" so that perl scripts are executed. * particular file type. For example on windows this can be set to "perl" so that perl scripts are executed.
* * <p/>
* The "Path" init param is passed to the exec environment as PATH. Note: Must * The "Path" init param is passed to the exec environment as PATH. Note: Must be run unpacked somewhere in the filesystem.
* be run unpacked somewhere in the filesystem. * <p/>
* * Any initParameter that starts with ENV_ is used to set an environment variable with the name stripped of the leading ENV_ and using the init parameter value.
* Any initParameter that starts with ENV_ is used to set an environment
* variable with the name stripped of the leading ENV_ and using the init
* parameter value.
*
*
*
*/ */
public class CGI extends HttpServlet public class CGI extends HttpServlet
{ {
/**
*
*/
private static final long serialVersionUID = -6182088932884791073L;
private static final Logger LOG = Log.getLogger(CGI.class); private static final Logger LOG = Log.getLogger(CGI.class);
private boolean _ok; private boolean _ok;
@ -63,81 +62,89 @@ public class CGI extends HttpServlet
private String _cmdPrefix; private String _cmdPrefix;
private EnvList _env; private EnvList _env;
private boolean _ignoreExitState; private boolean _ignoreExitState;
private boolean _relative;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@Override
public void init() throws ServletException public void init() throws ServletException
{ {
_env=new EnvList(); _env = new EnvList();
_cmdPrefix=getInitParameter("commandPrefix"); _cmdPrefix = getInitParameter("commandPrefix");
_relative = Boolean.parseBoolean(getInitParameter("cgibinResourceBaseIsRelative"));
String tmp=getInitParameter("cgibinResourceBase"); String tmp = getInitParameter("cgibinResourceBase");
if (tmp==null) if (tmp == null)
{ {
tmp=getInitParameter("resourceBase"); tmp = getInitParameter("resourceBase");
if (tmp==null) if (tmp == null)
tmp=getServletContext().getRealPath("/"); tmp = getServletContext().getRealPath("/");
}
else if (_relative)
{
tmp = getServletContext().getRealPath(tmp);
} }
if (tmp==null) if (tmp == null)
{ {
LOG.warn("CGI: no CGI bin !"); LOG.warn("CGI: no CGI bin !");
return; return;
} }
File dir=new File(tmp); File dir = new File(tmp);
if (!dir.exists()) if (!dir.exists())
{ {
LOG.warn("CGI: CGI bin does not exist - "+dir); LOG.warn("CGI: CGI bin does not exist - " + dir);
return; return;
} }
if (!dir.canRead()) if (!dir.canRead())
{ {
LOG.warn("CGI: CGI bin is not readable - "+dir); LOG.warn("CGI: CGI bin is not readable - " + dir);
return; return;
} }
if (!dir.isDirectory()) if (!dir.isDirectory())
{ {
LOG.warn("CGI: CGI bin is not a directory - "+dir); LOG.warn("CGI: CGI bin is not a directory - " + dir);
return; return;
} }
try try
{ {
_docRoot=dir.getCanonicalFile(); _docRoot = dir.getCanonicalFile();
} }
catch (IOException e) catch (IOException e)
{ {
LOG.warn("CGI: CGI bin failed - "+dir,e); LOG.warn("CGI: CGI bin failed - " + dir,e);
return; return;
} }
_path=getInitParameter("Path"); _path = getInitParameter("Path");
if (_path!=null) if (_path != null)
_env.set("PATH",_path); _env.set("PATH",_path);
_ignoreExitState="true".equalsIgnoreCase(getInitParameter("ignoreExitState")); _ignoreExitState = "true".equalsIgnoreCase(getInitParameter("ignoreExitState"));
Enumeration e=getInitParameterNames(); Enumeration e = getInitParameterNames();
while (e.hasMoreElements()) while (e.hasMoreElements())
{ {
String n=(String)e.nextElement(); String n = (String)e.nextElement();
if (n!=null&&n.startsWith("ENV_")) if (n != null && n.startsWith("ENV_"))
_env.set(n.substring(4),getInitParameter(n)); _env.set(n.substring(4),getInitParameter(n));
} }
if(!_env.envMap.containsKey("SystemRoot")) if (!_env.envMap.containsKey("SystemRoot"))
{ {
String os = System.getProperty("os.name"); String os = System.getProperty("os.name");
if (os!=null && os.toLowerCase().indexOf("windows")!=-1) if (os != null && os.toLowerCase().indexOf("windows") != -1)
{ {
_env.set("SystemRoot", "C:\\WINDOWS"); _env.set("SystemRoot","C:\\WINDOWS");
} }
} }
_ok=true; _ok = true;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@Override
public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
{ {
if (!_ok) if (!_ok)
@ -145,39 +152,38 @@ public class CGI extends HttpServlet
res.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE); res.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
return; return;
} }
String pathInContext=StringUtil.nonNull(req.getServletPath())+StringUtil.nonNull(req.getPathInfo());
String pathInContext = (_relative?"":StringUtil.nonNull(req.getServletPath())) + StringUtil.nonNull(req.getPathInfo());
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
{ {
LOG.debug("CGI: ContextPath : "+req.getContextPath()); LOG.debug("CGI: ContextPath : " + req.getContextPath());
LOG.debug("CGI: ServletPath : "+req.getServletPath()); LOG.debug("CGI: ServletPath : " + req.getServletPath());
LOG.debug("CGI: PathInfo : "+req.getPathInfo()); LOG.debug("CGI: PathInfo : " + req.getPathInfo());
LOG.debug("CGI: _docRoot : "+_docRoot); LOG.debug("CGI: _docRoot : " + _docRoot);
LOG.debug("CGI: _path : "+_path); LOG.debug("CGI: _path : " + _path);
LOG.debug("CGI: _ignoreExitState: "+_ignoreExitState); LOG.debug("CGI: _ignoreExitState: " + _ignoreExitState);
} }
// pathInContext may actually comprises scriptName/pathInfo...We will // pathInContext may actually comprises scriptName/pathInfo...We will
// walk backwards up it until we find the script - the rest must // walk backwards up it until we find the script - the rest must
// be the pathInfo; // be the pathInfo;
String both=pathInContext; String both = pathInContext;
String first=both; String first = both;
String last=""; String last = "";
File exe=new File(_docRoot,first); File exe = new File(_docRoot,first);
while ((first.endsWith("/")||!exe.exists())&&first.length()>=0) while ((first.endsWith("/") || !exe.exists()) && first.length() >= 0)
{ {
int index=first.lastIndexOf('/'); int index = first.lastIndexOf('/');
first=first.substring(0,index); first = first.substring(0,index);
last=both.substring(index,both.length()); last = both.substring(index,both.length());
exe=new File(_docRoot,first); exe = new File(_docRoot,first);
} }
if (first.length()==0||!exe.exists()||exe.isDirectory()||!exe.getCanonicalPath().equals(exe.getAbsolutePath())) if (first.length() == 0 || !exe.exists() || exe.isDirectory() || !exe.getCanonicalPath().equals(exe.getAbsolutePath()))
{ {
res.sendError(404); res.sendError(404);
} }
@ -185,8 +191,8 @@ public class CGI extends HttpServlet
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
{ {
LOG.debug("CGI: script is "+exe); LOG.debug("CGI: script is " + exe);
LOG.debug("CGI: pathInfo is "+last); LOG.debug("CGI: pathInfo is " + last);
} }
exec(exe,last,req,res); exec(exe,last,req,res);
} }
@ -198,19 +204,19 @@ public class CGI extends HttpServlet
*/ */
private void exec(File command, String pathInfo, HttpServletRequest req, HttpServletResponse res) throws IOException private void exec(File command, String pathInfo, HttpServletRequest req, HttpServletResponse res) throws IOException
{ {
String path=command.getAbsolutePath(); String path = command.getAbsolutePath();
File dir=command.getParentFile(); File dir = command.getParentFile();
String scriptName=req.getRequestURI().substring(0,req.getRequestURI().length()-pathInfo.length()); String scriptName = req.getRequestURI().substring(0,req.getRequestURI().length() - pathInfo.length());
String scriptPath=getServletContext().getRealPath(scriptName); String scriptPath = getServletContext().getRealPath(scriptName);
String pathTranslated=req.getPathTranslated(); String pathTranslated = req.getPathTranslated();
int len=req.getContentLength(); int len = req.getContentLength();
if (len<0) if (len < 0)
len=0; len = 0;
if ((pathTranslated==null)||(pathTranslated.length()==0)) if ((pathTranslated == null) || (pathTranslated.length() == 0))
pathTranslated=path; pathTranslated = path;
EnvList env=new EnvList(_env); EnvList env = new EnvList(_env);
// these ones are from "The WWW Common Gateway Interface Version 1.1" // these ones are from "The WWW Common Gateway Interface Version 1.1"
// look at : // look at :
// http://Web.Golux.Com/coar/cgi/draft-coar-cgi-v11-03-clean.html#6.1.1 // http://Web.Golux.Com/coar/cgi/draft-coar-cgi-v11-03-clean.html#6.1.1
@ -218,7 +224,7 @@ public class CGI extends HttpServlet
env.set("CONTENT_LENGTH",Integer.toString(len)); env.set("CONTENT_LENGTH",Integer.toString(len));
env.set("CONTENT_TYPE",req.getContentType()); env.set("CONTENT_TYPE",req.getContentType());
env.set("GATEWAY_INTERFACE","CGI/1.1"); env.set("GATEWAY_INTERFACE","CGI/1.1");
if ((pathInfo!=null)&&(pathInfo.length()>0)) if ((pathInfo != null) && (pathInfo.length() > 0))
{ {
env.set("PATH_INFO",pathInfo); env.set("PATH_INFO",pathInfo);
} }
@ -240,12 +246,12 @@ public class CGI extends HttpServlet
env.set("SERVER_PROTOCOL",req.getProtocol()); env.set("SERVER_PROTOCOL",req.getProtocol());
env.set("SERVER_SOFTWARE",getServletContext().getServerInfo()); env.set("SERVER_SOFTWARE",getServletContext().getServerInfo());
Enumeration enm=req.getHeaderNames(); Enumeration enm = req.getHeaderNames();
while (enm.hasMoreElements()) while (enm.hasMoreElements())
{ {
String name=(String)enm.nextElement(); String name = (String)enm.nextElement();
String value=req.getHeader(name); String value = req.getHeader(name);
env.set("HTTP_"+name.toUpperCase().replace('-','_'),value); env.set("HTTP_" + name.toUpperCase().replace('-','_'),value);
} }
// these extra ones were from printenv on www.dev.nomura.co.uk // these extra ones were from printenv on www.dev.nomura.co.uk
@ -257,28 +263,28 @@ public class CGI extends HttpServlet
// are we meant to decode args here ? or does the script get them // are we meant to decode args here ? or does the script get them
// via PATH_INFO ? if we are, they should be decoded and passed // via PATH_INFO ? if we are, they should be decoded and passed
// into exec here... // into exec here...
String execCmd=path; String execCmd = path;
if ((execCmd.charAt(0)!='"')&&(execCmd.indexOf(" ")>=0)) if ((execCmd.charAt(0) != '"') && (execCmd.indexOf(" ") >= 0))
execCmd="\""+execCmd+"\""; execCmd = "\"" + execCmd + "\"";
if (_cmdPrefix!=null) if (_cmdPrefix != null)
execCmd=_cmdPrefix+" "+execCmd; execCmd = _cmdPrefix + " " + execCmd;
Process p=(dir==null)?Runtime.getRuntime().exec(execCmd,env.getEnvArray()):Runtime.getRuntime().exec(execCmd,env.getEnvArray(),dir); Process p = (dir == null)?Runtime.getRuntime().exec(execCmd,env.getEnvArray()):Runtime.getRuntime().exec(execCmd,env.getEnvArray(),dir);
// hook processes input to browser's output (async) // hook processes input to browser's output (async)
final InputStream inFromReq=req.getInputStream(); final InputStream inFromReq = req.getInputStream();
final OutputStream outToCgi=p.getOutputStream(); final OutputStream outToCgi = p.getOutputStream();
final int inLength=len; final int inLength = len;
IO.copyThread(p.getErrorStream(),System.err); IO.copyThread(p.getErrorStream(),System.err);
new Thread(new Runnable() new Thread(new Runnable()
{ {
public void run() public void run()
{ {
try try
{ {
if (inLength>0) if (inLength > 0)
IO.copy(inFromReq,outToCgi,inLength); IO.copy(inFromReq,outToCgi,inLength);
outToCgi.close(); outToCgi.close();
} }
@ -296,28 +302,28 @@ public class CGI extends HttpServlet
{ {
// read any headers off the top of our input stream // read any headers off the top of our input stream
// NOTE: Multiline header items not supported! // NOTE: Multiline header items not supported!
String line=null; String line = null;
InputStream inFromCgi=p.getInputStream(); InputStream inFromCgi = p.getInputStream();
//br=new BufferedReader(new InputStreamReader(inFromCgi)); // br=new BufferedReader(new InputStreamReader(inFromCgi));
//while ((line=br.readLine())!=null) // while ((line=br.readLine())!=null)
while( (line = getTextLineFromStream( inFromCgi )).length() > 0 ) while ((line = getTextLineFromStream(inFromCgi)).length() > 0)
{ {
if (!line.startsWith("HTTP")) if (!line.startsWith("HTTP"))
{ {
int k=line.indexOf(':'); int k = line.indexOf(':');
if (k>0) if (k > 0)
{ {
String key=line.substring(0,k).trim(); String key = line.substring(0,k).trim();
String value = line.substring(k+1).trim(); String value = line.substring(k + 1).trim();
if ("Location".equals(key)) if ("Location".equals(key))
{ {
res.sendRedirect(res.encodeRedirectURL(value)); res.sendRedirect(res.encodeRedirectURL(value));
} }
else if ("Status".equals(key)) else if ("Status".equals(key))
{ {
String[] token = value.split( " " ); String[] token = value.split(" ");
int status=Integer.parseInt(token[0]); int status = Integer.parseInt(token[0]);
res.setStatus(status); res.setStatus(status);
} }
else else
@ -330,15 +336,15 @@ public class CGI extends HttpServlet
} }
// copy cgi content to response stream... // copy cgi content to response stream...
os = res.getOutputStream(); os = res.getOutputStream();
IO.copy(inFromCgi, os); IO.copy(inFromCgi,os);
p.waitFor(); p.waitFor();
if (!_ignoreExitState) if (!_ignoreExitState)
{ {
int exitValue=p.exitValue(); int exitValue = p.exitValue();
if (0!=exitValue) if (0 != exitValue)
{ {
LOG.warn("Non-zero exit status ("+exitValue+") from CGI program: "+path); LOG.warn("Non-zero exit status (" + exitValue + ") from CGI program: " + path);
if (!res.isCommitted()) if (!res.isCommitted())
res.sendError(500,"Failed to exec CGI"); res.sendError(500,"Failed to exec CGI");
} }
@ -356,13 +362,13 @@ public class CGI extends HttpServlet
} }
finally finally
{ {
if( os != null ) if (os != null)
{ {
try try
{ {
os.close(); os.close();
} }
catch(Exception e) catch (Exception e)
{ {
LOG.ignore(e); LOG.ignore(e);
} }
@ -375,19 +381,24 @@ public class CGI extends HttpServlet
/** /**
* Utility method to get a line of text from the input stream. * Utility method to get a line of text from the input stream.
* @param is the input stream *
* @param is
* the input stream
* @return the line of text * @return the line of text
* @throws IOException * @throws IOException
*/ */
private String getTextLineFromStream( InputStream is ) throws IOException { private String getTextLineFromStream(InputStream is) throws IOException
{
StringBuilder buffer = new StringBuilder(); StringBuilder buffer = new StringBuilder();
int b; int b;
while( (b = is.read()) != -1 && b != (int) '\n' ) { while ((b = is.read()) != -1 && b != '\n')
buffer.append( (char) b ); {
} buffer.append((char)b);
return buffer.toString().trim(); }
return buffer.toString().trim();
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* private utility class that manages the Environment passed to exec. * private utility class that manages the Environment passed to exec.
@ -398,12 +409,12 @@ public class CGI extends HttpServlet
EnvList() EnvList()
{ {
envMap=new HashMap(); envMap = new HashMap();
} }
EnvList(EnvList l) EnvList(EnvList l)
{ {
envMap=new HashMap(l.envMap); envMap = new HashMap(l.envMap);
} }
/** /**
@ -411,7 +422,7 @@ public class CGI extends HttpServlet
*/ */
public void set(String name, String value) public void set(String name, String value)
{ {
envMap.put(name,name+"="+StringUtil.nonNull(value)); envMap.put(name,name + "=" + StringUtil.nonNull(value));
} }
/** Get representation suitable for passing to exec. */ /** Get representation suitable for passing to exec. */
@ -420,6 +431,7 @@ public class CGI extends HttpServlet
return (String[])envMap.values().toArray(new String[envMap.size()]); return (String[])envMap.values().toArray(new String[envMap.size()]);
} }
@Override
public String toString() public String toString()
{ {
return envMap.toString(); return envMap.toString();

View File

@ -29,7 +29,7 @@
</configuration> </configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin> <plugin>
<!-- <!--
Required for OSGI Required for OSGI
@ -37,7 +37,7 @@
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId> <artifactId>maven-jar-plugin</artifactId>
<configuration> <configuration>
<archive> <archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile> <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive> </archive>
</configuration> </configuration>
@ -70,9 +70,9 @@
</build> </build>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.eclipse.jetty.toolchain</groupId> <groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId> <artifactId>jetty-test-helper</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
@ -80,5 +80,16 @@
<scope>provided</scope> <scope>provided</scope>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<!--
This dependency is used to test Slf4jLog.
Due to the introduction of src/test/resource/jetty-logging.properties (and the Log.static{} block)
the default Log implementation is still StdErrLog during testing.
-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>${slf4j-version}</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -24,17 +24,13 @@ import java.util.logging.Level;
* </p> * </p>
* *
* <p> * <p>
* Honors the standard jetty system property <code>"org.eclipse.jetty.util.log.DEBUG"</code> to set logger into debug
* mode (defaults to false, set to "true" to enable)
* </p>
*
* <p>
* You can also set the logger level using <a href="http://java.sun.com/j2se/1.5.0/docs/guide/logging/overview.html"> * You can also set the logger level using <a href="http://java.sun.com/j2se/1.5.0/docs/guide/logging/overview.html">
* standard java.util.logging configuration</a> against the name <code>"org.eclipse.jetty.util.log"</code>. * standard java.util.logging configuration</a>.
* </p> * </p>
*/ */
public class JavaUtilLog implements Logger public class JavaUtilLog implements Logger
{ {
private Level configuredLevel;
private java.util.logging.Logger _logger; private java.util.logging.Logger _logger;
public JavaUtilLog() public JavaUtilLog()
@ -45,8 +41,11 @@ public class JavaUtilLog implements Logger
public JavaUtilLog(String name) public JavaUtilLog(String name)
{ {
_logger = java.util.logging.Logger.getLogger(name); _logger = java.util.logging.Logger.getLogger(name);
if (Boolean.getBoolean("org.eclipse.jetty.util.log.DEBUG")) if (Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.DEBUG", "false")))
{
_logger.setLevel(Level.FINE); _logger.setLevel(Level.FINE);
}
configuredLevel = _logger.getLevel();
} }
public String getName() public String getName()
@ -91,7 +90,15 @@ public class JavaUtilLog implements Logger
public void setDebugEnabled(boolean enabled) public void setDebugEnabled(boolean enabled)
{ {
_logger.setLevel(Level.FINE); if (enabled)
{
configuredLevel = _logger.getLevel();
_logger.setLevel(Level.FINE);
}
else
{
_logger.setLevel(configuredLevel);
}
} }
public void debug(String msg, Object... args) public void debug(String msg, Object... args)

View File

@ -13,10 +13,16 @@
package org.eclipse.jetty.util.log; package org.eclipse.jetty.util.log;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.URL;
import java.security.AccessController; import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.util.Enumeration;
import java.util.Properties;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.Loader;
/** /**
@ -40,17 +46,69 @@ public class Log
public final static String EXCEPTION= "EXCEPTION "; public final static String EXCEPTION= "EXCEPTION ";
public final static String IGNORED= "IGNORED "; public final static String IGNORED= "IGNORED ";
/**
* Logging Configuration Properties
*/
protected static Properties __props;
/**
* The {@link Logger} implementation class name
*/
public static String __logClass; public static String __logClass;
/**
* Legacy flag indicating if {@link Log#ignore(Throwable)} methods produce any output in the {@link Logger}s
*/
public static boolean __ignored; public static boolean __ignored;
static static
{ {
/* Instantiate a default configuration properties (empty)
*/
__props = new Properties();
AccessController.doPrivileged(new PrivilegedAction<Object>() AccessController.doPrivileged(new PrivilegedAction<Object>()
{ {
public Object run() public Object run()
{ {
__logClass = System.getProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.Slf4jLog"); /* First see if the jetty-logging.properties object exists in the classpath.
__ignored = Boolean.parseBoolean(System.getProperty("org.eclipse.jetty.util.log.IGNORED", "false")); * This is an optional feature used by embedded mode use, and test cases to allow for early
* configuration of the Log class in situations where access to the System.properties are
* either too late or just impossible.
*/
URL testProps = Log.class.getClassLoader().getResource("jetty-logging.properties");
if (testProps != null)
{
InputStream in = null;
try
{
in = testProps.openStream();
__props.load(in);
}
catch (IOException e)
{
System.err.println("Unable to load " + testProps);
e.printStackTrace(System.err);
}
finally
{
IO.close(in);
}
}
/* Now load the System.properties as-is into the __props, these values will override
* any key conflicts in __props.
*/
@SuppressWarnings("unchecked")
Enumeration<String> systemKeyEnum = (Enumeration<String>)System.getProperties().propertyNames();
while (systemKeyEnum.hasMoreElements())
{
String key = systemKeyEnum.nextElement();
__props.setProperty(key,System.getProperty(key));
}
/* Now use the configuration properties to configure the Log statics
*/
__logClass = __props.getProperty("org.eclipse.jetty.util.log.class","org.eclipse.jetty.util.log.Slf4jLog");
__ignored = Boolean.parseBoolean(__props.getProperty("org.eclipse.jetty.util.log.IGNORED","false"));
return null; return null;
} }
}); });
@ -62,12 +120,16 @@ public class Log
public static boolean initialized() public static boolean initialized()
{ {
if (LOG != null) if (LOG != null)
{
return true; return true;
}
synchronized (Log.class) synchronized (Log.class)
{ {
if (__initialized) if (__initialized)
{
return LOG != null; return LOG != null;
}
__initialized = true; __initialized = true;
} }
@ -80,11 +142,14 @@ public class Log
LOG.debug("Logging to {} via {}", LOG, log_class.getName()); LOG.debug("Logging to {} via {}", LOG, log_class.getName());
} }
} }
catch(ThreadDeath e)
{
// Let ThreadDeath pass through
throw e;
}
catch(Throwable e) catch(Throwable e)
{ {
if (e instanceof ThreadDeath) // Unable to load specified Logger implementation, default to standard logging.
throw (ThreadDeath)e;
initStandardLogging(e); initStandardLogging(e);
} }
@ -95,7 +160,10 @@ public class Log
{ {
Class<?> log_class; Class<?> log_class;
if(e != null && __ignored) if(e != null && __ignored)
{
e.printStackTrace(); e.printStackTrace();
}
if (LOG == null) if (LOG == null)
{ {
log_class = StdErrLog.class; log_class = StdErrLog.class;

View File

@ -25,7 +25,6 @@ public class Slf4jLog implements Logger
public Slf4jLog() throws Exception public Slf4jLog() throws Exception
{ {
this("org.eclipse.jetty.util.log"); this("org.eclipse.jetty.util.log");
} }
public Slf4jLog(String name) public Slf4jLog(String name)
@ -42,6 +41,7 @@ public class Slf4jLog implements Logger
} }
org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger( name ); org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger( name );
// Fix LocationAwareLogger use to indicate FQCN of this class - // Fix LocationAwareLogger use to indicate FQCN of this class -
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=276670 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=276670
if (logger instanceof org.slf4j.spi.LocationAwareLogger) if (logger instanceof org.slf4j.spi.LocationAwareLogger)

View File

@ -38,20 +38,24 @@ import org.eclipse.jetty.util.DateCache;
public class StdErrLog implements Logger public class StdErrLog implements Logger
{ {
private static DateCache _dateCache; private static DateCache _dateCache;
private static Properties __props = Log.__props;
private final static boolean __source = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.SOURCE",
Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.SOURCE","false")));
private final static boolean __long = Boolean.parseBoolean(Log.__props.getProperty("org.eclipse.jetty.util.log.stderr.LONG","false"));
private final static boolean __source = Boolean.parseBoolean(System.getProperty("org.eclipse.jetty.util.log.SOURCE", /**
System.getProperty("org.eclipse.jetty.util.log.stderr.SOURCE","false"))); * Tracking for child loggers only.
private final static boolean __long = Boolean.parseBoolean(System.getProperty("org.eclipse.jetty.util.log.stderr.LONG","false")); */
private final static ConcurrentMap<String, StdErrLog> __loggers = new ConcurrentHashMap<String, StdErrLog>(); private final static ConcurrentMap<String, StdErrLog> __loggers = new ConcurrentHashMap<String, StdErrLog>();
static static
{ {
String deprecatedProperites[] = String deprecatedProperties[] =
{ "DEBUG", "org.eclipse.jetty.util.log.DEBUG", "org.eclipse.jetty.util.log.stderr.DEBUG" }; { "DEBUG", "org.eclipse.jetty.util.log.DEBUG", "org.eclipse.jetty.util.log.stderr.DEBUG" };
// Toss a message to users about deprecated system properties // Toss a message to users about deprecated system properties
for (String deprecatedProp : deprecatedProperites) for (String deprecatedProp : deprecatedProperties)
{ {
if (System.getProperty(deprecatedProp) != null) if (System.getProperty(deprecatedProp) != null)
{ {
@ -75,6 +79,8 @@ public class StdErrLog implements Logger
public static final int LEVEL_WARN = 3; public static final int LEVEL_WARN = 3;
private int _level = LEVEL_INFO; private int _level = LEVEL_INFO;
// Level that this Logger was configured as (remembered in special case of .setDebugEnabled())
private int _configuredLevel;
private PrintStream _stderr = System.err; private PrintStream _stderr = System.err;
private boolean _source = __source; private boolean _source = __source;
// Print the long form names, otherwise use abbreviated // Print the long form names, otherwise use abbreviated
@ -92,13 +98,20 @@ public class StdErrLog implements Logger
public StdErrLog(String name) public StdErrLog(String name)
{ {
this(name,__props);
}
public StdErrLog(String name, Properties props)
{
__props = props;
this._name = name == null?"":name; this._name = name == null?"":name;
this._abbrevname = condensePackageString(this._name); this._abbrevname = condensePackageString(this._name);
this._level = getLoggingLevel(System.getProperties(),this._name); this._level = getLoggingLevel(props,this._name);
this._configuredLevel = this._level;
try try
{ {
_source = Boolean.parseBoolean(System.getProperty(_name + ".SOURCE",Boolean.toString(_source))); _source = Boolean.parseBoolean(props.getProperty(_name + ".SOURCE",Boolean.toString(_source)));
} }
catch (AccessControlException ace) catch (AccessControlException ace)
{ {
@ -126,34 +139,29 @@ public class StdErrLog implements Logger
{ {
String levelStr = props.getProperty(nameSegment + ".LEVEL"); String levelStr = props.getProperty(nameSegment + ".LEVEL");
// System.err.printf("[StdErrLog.CONFIG] Checking for property [%s.LEVEL] = %s%n",nameSegment,levelStr); // System.err.printf("[StdErrLog.CONFIG] Checking for property [%s.LEVEL] = %s%n",nameSegment,levelStr);
if (levelStr == null) int level = getLevelId(nameSegment + ".LEVEL",levelStr);
if (level != (-1))
{ {
// Trim and try again. return level;
int idx = nameSegment.lastIndexOf('.'); }
if (idx >= 0)
{ // Trim and try again.
nameSegment = nameSegment.substring(0,idx); int idx = nameSegment.lastIndexOf('.');
} if (idx >= 0)
else {
{ nameSegment = nameSegment.substring(0,idx);
nameSegment = null;
}
} }
else else
{ {
int level = getLevelId(levelStr); nameSegment = null;
if (level != (-1))
{
return level;
}
} }
} }
// Default Logging Level // Default Logging Level
return getLevelId(props.getProperty("log.LEVEL", "INFO")); return getLevelId("log.LEVEL",props.getProperty("log.LEVEL","INFO"));
} }
protected static int getLevelId(String levelName) protected static int getLevelId(String levelSegment, String levelName)
{ {
if (levelName == null) if (levelName == null)
{ {
@ -177,7 +185,7 @@ public class StdErrLog implements Logger
return LEVEL_WARN; return LEVEL_WARN;
} }
System.err.println("Unknown StdErrLog level [" + levelStr + "], expecting only [ALL, DEBUG, INFO, WARN] as values."); System.err.println("Unknown StdErrLog level [" + levelSegment + "]=[" + levelStr + "], expecting only [ALL, DEBUG, INFO, WARN] as values.");
return -1; return -1;
} }
@ -316,18 +324,36 @@ public class StdErrLog implements Logger
} }
/** /**
* @deprecated use {@link #setLevel(int)} instead. * Legacy interface where a programmatic configuration of the logger level
* is done as a wholesale approach.
*/ */
@Deprecated
public void setDebugEnabled(boolean enabled) public void setDebugEnabled(boolean enabled)
{ {
if (enabled) if (enabled)
{ {
_level = LEVEL_DEBUG; synchronized (__loggers)
{
this._level = LEVEL_DEBUG;
// Boot stomp all cached log levels to DEBUG
for(StdErrLog log: __loggers.values())
{
log._level = LEVEL_DEBUG;
}
}
} }
else else
{ {
_level = LEVEL_INFO; synchronized (__loggers)
{
this._level = this._configuredLevel;
// restore all cached log configured levels
for(StdErrLog log: __loggers.values())
{
log._level = log._configuredLevel;
}
}
} }
} }
@ -543,16 +569,53 @@ public class StdErrLog implements Logger
} }
} }
/**
* A more robust form of name blank test. Will return true for null names, and names that have only whitespace
*
* @param name
* the name to test
* @return true for null or blank name, false if any non-whitespace character is found.
*/
private static boolean isBlank(String name)
{
if (name == null)
{
return true;
}
int size = name.length();
char c;
for (int i = 0; i < size; i++)
{
c = name.charAt(i);
if (!Character.isWhitespace(c))
{
return false;
}
}
return true;
}
/**
* Get a Child Logger relative to this Logger.
*
* @param name
* the child name
* @return the appropriate child logger (if name specified results in a new unique child)
*/
public Logger getLogger(String name) public Logger getLogger(String name)
{ {
String fullname = _name == null || _name.length() == 0?name:_name + "." + name; if (isBlank(name))
if ((name == null && this._name == null) || fullname.equals(_name))
{ {
return this; return this;
} }
StdErrLog logger = __loggers.get(name); String fullname = name;
if (!isBlank(_name))
{
fullname = _name + "." + name;
}
StdErrLog logger = __loggers.get(fullname);
if (logger == null) if (logger == null)
{ {
StdErrLog sel = new StdErrLog(fullname); StdErrLog sel = new StdErrLog(fullname);
@ -560,6 +623,7 @@ public class StdErrLog implements Logger
sel.setPrintLongNames(_printLongNames); sel.setPrintLongNames(_printLongNames);
// Let Level come from configured Properties instead - sel.setLevel(_level); // Let Level come from configured Properties instead - sel.setLevel(_level);
sel.setSource(_source); sel.setSource(_source);
sel._stderr = this._stderr;
logger = __loggers.putIfAbsent(fullname,sel); logger = __loggers.putIfAbsent(fullname,sel);
if (logger == null) if (logger == null)
{ {
@ -597,6 +661,11 @@ public class StdErrLog implements Logger
} }
return s.toString(); return s.toString();
} }
public static void setProperties(Properties props)
{
__props = props;
}
public void ignore(Throwable ignored) public void ignore(Throwable ignored)
{ {

View File

@ -24,7 +24,8 @@ public class Utf8StringBuilderTest
public void testInvalid() throws Exception public void testInvalid() throws Exception
{ {
String[] invalids = String[] invalids =
{ "c0af", "EDA080", "f08080af", "f8808080af", "e080af", "F4908080", "fbbfbfbfbf", "10FFFF" }; { "c0af", "EDA080", "f08080af", "f8808080af", "e080af", "F4908080", "fbbfbfbfbf", "10FFFF",
"CeBaE1BdB9Cf83CeBcCeB5EdA080656469746564" };
for (String i : invalids) for (String i : invalids)
{ {

View File

@ -0,0 +1,69 @@
package org.eclipse.jetty.util.log;
import static org.hamcrest.Matchers.*;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import org.eclipse.jetty.util.IO;
import org.junit.Assert;
public class CapturingJULHandler extends Handler
{
private static final String LN = System.getProperty("line.separator");
private StringBuilder output = new StringBuilder();
@Override
public void publish(LogRecord record)
{
StringBuilder buf = new StringBuilder();
buf.append(record.getLevel().getName()).append("|");
buf.append(record.getLoggerName()).append("|");
buf.append(record.getMessage());
output.append(buf);
if (record.getMessage().length() > 0)
{
output.append(LN);
}
if (record.getThrown() != null)
{
StringWriter sw = new StringWriter(128);
PrintWriter capture = new PrintWriter(sw);
record.getThrown().printStackTrace(capture);
capture.flush();
output.append(sw.toString());
IO.close(capture);
}
}
public void clear()
{
output.setLength(0);
}
@Override
public void flush()
{
/* do nothing */
}
@Override
public void close() throws SecurityException
{
/* do nothing */
}
public void dump()
{
System.out.println(output);
}
public void assertContainsLine(String line)
{
Assert.assertThat(output.toString(),containsString(line));
}
}

View File

@ -0,0 +1,224 @@
package org.eclipse.jetty.util.log;
import static org.hamcrest.Matchers.*;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
public class JavaUtilLogTest
{
private static Handler[] originalHandlers;
private static CapturingJULHandler jul;
@BeforeClass
public static void setJUL()
{
LogManager lmgr = LogManager.getLogManager();
java.util.logging.Logger root = lmgr.getLogger("");
// Remember original handlers
originalHandlers = root.getHandlers();
// Remove original handlers
for (Handler existing : originalHandlers)
{
root.removeHandler(existing);
}
// Set test/capturing handler
jul = new CapturingJULHandler();
root.addHandler(jul);
}
@AfterClass
public static void restoreJUL()
{
LogManager lmgr = LogManager.getLogManager();
java.util.logging.Logger root = lmgr.getLogger("");
// Remove test handlers
for (Handler existing : root.getHandlers())
{
root.removeHandler(existing);
}
// Restore original handlers
for (Handler original : originalHandlers)
{
root.addHandler(original);
}
}
@Test
public void testNamedLogger()
{
jul.clear();
JavaUtilLog log = new JavaUtilLog("test");
log.info("Info test");
jul.assertContainsLine("INFO|test|Info test");
JavaUtilLog loglong = new JavaUtilLog("test.a.long.name");
loglong.info("Long test");
jul.assertContainsLine("INFO|test.a.long.name|Long test");
}
@Test
public void testDebugOutput()
{
jul.clear();
// Common Throwable (for test)
Throwable th = new Throwable("Message");
// Capture raw string form
StringWriter tout = new StringWriter();
th.printStackTrace(new PrintWriter(tout));
String ths = tout.toString();
// Tests
JavaUtilLog log = new JavaUtilLog("test.de.bug");
setJulLevel("test.de.bug",Level.FINE);
log.debug("Simple debug");
log.debug("Debug with {} parameter",1);
log.debug("Debug with {} {} parameters", 2, "spiffy");
log.debug("Debug with throwable", th);
log.debug(th);
// jul.dump();
jul.assertContainsLine("FINE|test.de.bug|Simple debug");
jul.assertContainsLine("FINE|test.de.bug|Debug with 1 parameter");
jul.assertContainsLine("FINE|test.de.bug|Debug with 2 spiffy parameters");
jul.assertContainsLine("FINE|test.de.bug|Debug with throwable");
jul.assertContainsLine(ths);
}
@Test
public void testInfoOutput()
{
jul.clear();
// Common Throwable (for test)
Throwable th = new Throwable("Message");
// Capture raw string form
StringWriter tout = new StringWriter();
th.printStackTrace(new PrintWriter(tout));
String ths = tout.toString();
// Tests
JavaUtilLog log = new JavaUtilLog("test.in.fo");
setJulLevel("test.in.fo",Level.INFO);
log.info("Simple info");
log.info("Info with {} parameter",1);
log.info("Info with {} {} parameters", 2, "spiffy");
log.info("Info with throwable", th);
log.info(th);
// jul.dump();
jul.assertContainsLine("INFO|test.in.fo|Simple info");
jul.assertContainsLine("INFO|test.in.fo|Info with 1 parameter");
jul.assertContainsLine("INFO|test.in.fo|Info with 2 spiffy parameters");
jul.assertContainsLine("INFO|test.in.fo|Info with throwable");
jul.assertContainsLine(ths);
}
@Test
public void testWarnOutput()
{
jul.clear();
// Common Throwable (for test)
Throwable th = new Throwable("Message");
// Capture raw string form
StringWriter tout = new StringWriter();
th.printStackTrace(new PrintWriter(tout));
String ths = tout.toString();
// Tests
JavaUtilLog log = new JavaUtilLog("test.wa.rn");
setJulLevel("test.wa.rn",Level.WARNING);
log.warn("Simple warn");
log.warn("Warn with {} parameter",1);
log.warn("Warn with {} {} parameters", 2, "spiffy");
log.warn("Warn with throwable", th);
log.warn(th);
// jul.dump();
jul.assertContainsLine("WARNING|test.wa.rn|Simple warn");
jul.assertContainsLine("WARNING|test.wa.rn|Warn with 1 parameter");
jul.assertContainsLine("WARNING|test.wa.rn|Warn with 2 spiffy parameters");
jul.assertContainsLine("WARNING|test.wa.rn|Warn with throwable");
jul.assertContainsLine(ths);
}
@Test
public void testFormattingWithNulls()
{
jul.clear();
JavaUtilLog log = new JavaUtilLog("test.nu.ll");
setJulLevel("test.nu.ll",Level.INFO);
log.info("Testing info(msg,null,null) - {} {}","arg0","arg1");
log.info("Testing info(msg,null,null) - {}/{}",null,null);
log.info("Testing info(msg,null,null) > {}",null,null);
log.info("Testing info(msg,null,null)",null,null);
log.info(null,"Testing","info(null,arg0,arg1)");
log.info(null,null,null);
jul.dump();
jul.assertContainsLine("INFO|test.nu.ll|Testing info(msg,null,null) - null/null");
jul.assertContainsLine("INFO|test.nu.ll|Testing info(msg,null,null) > null null");
jul.assertContainsLine("INFO|test.nu.ll|Testing info(msg,null,null) null null");
jul.assertContainsLine("INFO|test.nu.ll|null Testing info(null,arg0,arg1)");
jul.assertContainsLine("INFO|test.nu.ll|null null null");
}
@Test
public void testIsDebugEnabled() {
JavaUtilLog log = new JavaUtilLog("test.legacy");
setJulLevel("test.legacy",Level.ALL);
Assert.assertThat("log.level(all).isDebugEnabled", log.isDebugEnabled(), is(true));
setJulLevel("test.legacy",Level.FINEST);
Assert.assertThat("log.level(finest).isDebugEnabled", log.isDebugEnabled(), is(true));
setJulLevel("test.legacy",Level.FINER);
Assert.assertThat("log.level(finer).isDebugEnabled", log.isDebugEnabled(), is(true));
setJulLevel("test.legacy",Level.FINE);
Assert.assertThat("log.level(fine).isDebugEnabled", log.isDebugEnabled(), is(true));
setJulLevel("test.legacy",Level.INFO);
Assert.assertThat("log.level(info).isDebugEnabled", log.isDebugEnabled(), is(false));
setJulLevel("test.legacy",Level.WARNING);
Assert.assertThat("log.level(warn).isDebugEnabled", log.isDebugEnabled(), is(false));
log.setDebugEnabled(true);
Assert.assertThat("log.isDebugEnabled", log.isDebugEnabled(), is(true));
log.setDebugEnabled(false);
Assert.assertThat("log.isDebugEnabled", log.isDebugEnabled(), is(false));
}
private void setJulLevel(String name, Level lvl)
{
java.util.logging.Logger log = java.util.logging.Logger.getLogger(name);
log.setLevel(lvl);
}
}

View File

@ -13,165 +13,71 @@
package org.eclipse.jetty.util.log; package org.eclipse.jetty.util.log;
import static org.junit.Assert.assertTrue; import static org.hamcrest.Matchers.*;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import junit.framework.Assert;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
public class LogTest public class LogTest
{ {
static PrintStream _orig= System.err; private static Logger originalLogger;
static ByteArrayOutputStream _out = new ByteArrayOutputStream();
static PrintStream _pout = new PrintStream(_out);
@SuppressWarnings("deprecation")
@BeforeClass @BeforeClass
public static void setUp() public static void rememberOriginalLogger()
{ {
System.setErr(_pout); originalLogger = Log.getLog();
} }
@AfterClass @AfterClass
public static void tearDown() public static void restoreOriginalLogger()
{ {
System.setErr(_orig); Log.setLog(originalLogger);
}
private void logNotContains(String text)
{
_pout.flush();
String err = _out.toString();
_out.reset();
if (err.indexOf(text)<0)
return;
_orig.println("FAIL '"+text+"' in '"+err+"'");
assertTrue(false);
}
private void logContains(String text)
{
_pout.flush();
String err = _out.toString();
_out.reset();
err = err.replaceAll("\r\n","\n");
text = text.replaceAll("\r\n","\n");
if (err.indexOf(text)>=0)
return;
_orig.println("FAIL '"+text+"' not in '"+err+"'");
assertTrue(false);
} }
@Test @Test
public void testStdErrLogFormat() public void testDefaultLogging()
{ {
StdErrLog log = new StdErrLog(LogTest.class.getName()); Logger log = Log.getLogger(LogTest.class);
log.info("Test default logging");
}
log.info("testing:{},{}","test","format"); // @Test
logContains("INFO:oejul.LogTest:testing:test,format"); public void testNamedLogNamed_StdErrLog()
{
log.info("testing:{}","test","format"); Log.setLog(new StdErrLog());
logContains("INFO:oejul.LogTest:testing:test format");
assertNamedLogging(Red.class);
log.info("testing","test","format"); assertNamedLogging(Blue.class);
logContains("INFO:oejul.LogTest:testing test format"); assertNamedLogging(Green.class);
}
log.info("testing:{},{}","test",null);
logContains("INFO:oejul.LogTest:testing:test,null"); @Test
public void testNamedLogNamed_JUL()
log.info("testing {} {}",null,null); {
logContains("INFO:oejul.LogTest:testing null null"); Log.setLog(new JavaUtilLog());
log.info("testing:{}",null,null); assertNamedLogging(Red.class);
logContains("INFO:oejul.LogTest:testing:null"); assertNamedLogging(Blue.class);
assertNamedLogging(Green.class);
log.info("testing",null,null); }
logContains("INFO:oejul.LogTest:testing");
@Test
public void testNamedLogNamed_Slf4J() throws Exception
{
Log.setLog(new Slf4jLog());
assertNamedLogging(Red.class);
assertNamedLogging(Blue.class);
assertNamedLogging(Green.class);
} }
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Test private void assertNamedLogging(Class<?> clazz)
public void testStdErrLogDebug()
{ {
StdErrLog log = new StdErrLog("xxx"); Logger lc = Log.getLogger(clazz);
Assert.assertThat("Named logging (impl=" + Log.getLog().getClass().getName() + ")",lc.getName(),is(clazz.getName()));
log.setLevel(StdErrLog.LEVEL_DEBUG);
log.debug("testing {} {}","test","debug");
logContains("DBUG:xxx:testing test debug");
log.info("testing {} {}","test","info");
logContains("INFO:xxx:testing test info");
log.warn("testing {} {}","test","warn");
logContains("WARN:xxx:testing test warn");
log.setLevel(StdErrLog.LEVEL_INFO);
log.debug("YOU SHOULD NOT SEE THIS!",null,null);
logNotContains("YOU SHOULD NOT SEE THIS!");
// Test for backward compat with old (now deprecated) method
log.setDebugEnabled(true);
log.debug("testing {} {}","test","debug-deprecated");
logContains("DBUG:xxx:testing test debug-deprecated");
log.setDebugEnabled(false);
log.debug("testing {} {}","test","debug-deprecated-false");
logNotContains("DBUG:xxx:testing test debug-depdeprecated-false");
}
@Test
public void testStdErrLogName()
{
StdErrLog log = new StdErrLog("test");
log.setPrintLongNames(true);
Assert.assertEquals("test",log.getName());
Logger next=log.getLogger("next");
Assert.assertEquals("test.next",next.getName());
next.info("testing {} {}","next","info");
logContains(":test.next:testing next info");
}
@Test
public void testStdErrThrowable()
{
Throwable th = new Throwable("Message");
th.printStackTrace();
_pout.flush();
String ths = _out.toString();
_out.reset();
StdErrLog log = new StdErrLog("test");
log.warn("ex",th);
logContains(ths);
th = new Throwable("Message with \033 escape");
log.warn("ex",th);
logNotContains("Message with \033 escape");
log.info(th.toString());
logNotContains("Message with \033 escape");
log.warn("ex",th);
logContains("Message with ? escape");
log.info(th.toString());
logContains("Message with ? escape");
} }
} }

View File

@ -1,43 +1,24 @@
package org.eclipse.jetty.util.log; package org.eclipse.jetty.util.log;
import static org.hamcrest.Matchers.containsString; import org.junit.AfterClass;
import org.junit.BeforeClass;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
public class NamedLogTest public class NamedLogTest
{ {
private PrintStream orig; private static Logger originalLogger;
private ByteArrayOutputStream logstream;
private PrintStream perr;
private Logger origLogger;
@Before @SuppressWarnings("deprecation")
public void setUp() @BeforeClass
public static void rememberOriginalLogger()
{ {
origLogger = Log.getRootLogger(); originalLogger = Log.getLog();
orig = System.err;
logstream = new ByteArrayOutputStream();
perr = new PrintStream(logstream);
System.setErr(perr);
StdErrLog logger = new StdErrLog();
Log.setLog(logger);
} }
@After @AfterClass
public void tearDown() public static void restoreOriginalLogger()
{ {
System.out.println(logstream.toString()); Log.setLog(originalLogger);
System.setErr(orig);
Log.setLog(origLogger);
} }
@Test @Test
@ -46,29 +27,32 @@ public class NamedLogTest
Red red = new Red(); Red red = new Red();
Green green = new Green(); Green green = new Green();
Blue blue = new Blue(); Blue blue = new Blue();
setLoggerOptions(Red.class); StdErrCapture output = new StdErrCapture();
setLoggerOptions(Green.class);
setLoggerOptions(Blue.class); setLoggerOptions(Red.class,output);
setLoggerOptions(Green.class,output);
setLoggerOptions(Blue.class,output);
red.generateLogs(); red.generateLogs();
green.generateLogs(); green.generateLogs();
blue.generateLogs(); blue.generateLogs();
String rawlog = logstream.toString(); output.assertContains(Red.class.getName());
output.assertContains(Green.class.getName());
Assert.assertThat(rawlog,containsString(Red.class.getName())); output.assertContains(Blue.class.getName());
Assert.assertThat(rawlog,containsString(Green.class.getName()));
Assert.assertThat(rawlog,containsString(Blue.class.getName()));
} }
private void setLoggerOptions(Class<?> clazz) private void setLoggerOptions(Class<?> clazz, StdErrCapture output)
{ {
Logger logger = Log.getLogger(clazz); Logger logger = Log.getLogger(clazz);
logger.setDebugEnabled(true); logger.setDebugEnabled(true);
if(logger instanceof StdErrLog) { if (logger instanceof StdErrLog)
((StdErrLog)logger).setPrintLongNames(true); {
StdErrLog sel = (StdErrLog)logger;
sel.setPrintLongNames(true);
output.capture(sel);
} }
} }
} }

View File

@ -0,0 +1,42 @@
package org.eclipse.jetty.util.log;
import java.io.File;
import java.io.FileFilter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.junit.Assume;
public final class Slf4jHelper
{
public static ClassLoader createTestClassLoader(ClassLoader parentClassLoader) throws MalformedURLException
{
File testJarDir = MavenTestingUtils.getTargetFile("test-jars");
Assume.assumeTrue(testJarDir.exists()); // trigger @Ignore if dir not there
File jarfiles[] = testJarDir.listFiles(new FileFilter()
{
public boolean accept(File path)
{
if (!path.isFile())
{
return false;
}
return path.getName().endsWith(".jar");
}
});
Assume.assumeTrue(jarfiles.length > 0); // trigger @Ignore if no jar files.
URL urls[] = new URL[jarfiles.length];
for (int i = 0; i < jarfiles.length; i++)
{
urls[i] = jarfiles[i].toURI().toURL();
// System.out.println("Adding test-jar => " + urls[i]);
}
return new URLClassLoader(urls,parentClassLoader);
}
}

View File

@ -0,0 +1,45 @@
package org.eclipse.jetty.util.log;
import static org.hamcrest.Matchers.*;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import org.junit.Assert;
public class StdErrCapture
{
private ByteArrayOutputStream test;
private PrintStream err;
public StdErrCapture(StdErrLog log)
{
this();
log.setStdErrStream(err);
}
public StdErrCapture()
{
test = new ByteArrayOutputStream();
err = new PrintStream(test);
}
public void capture(StdErrLog log)
{
log.setStdErrStream(err);
}
public void assertContains(String expectedString)
{
err.flush();
String output = new String(test.toByteArray());
Assert.assertThat(output,containsString(expectedString));
}
public void assertNotContains(String unexpectedString)
{
err.flush();
String output = new String(test.toByteArray());
Assert.assertThat(output,not(containsString(unexpectedString)));
}
}

View File

@ -18,6 +18,8 @@ import static org.hamcrest.Matchers.not;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.Properties; import java.util.Properties;
@ -29,6 +31,104 @@ import org.junit.Test;
*/ */
public class StdErrLogTest public class StdErrLogTest
{ {
@Test
public void testStdErrLogFormat() throws UnsupportedEncodingException
{
StdErrLog log = new StdErrLog(LogTest.class.getName());
StdErrCapture output = new StdErrCapture(log);
log.info("testing:{},{}","test","format1");
log.info("testing:{}","test","format2");
log.info("testing","test","format3");
log.info("testing:{},{}","test",null);
log.info("testing {} {}",null,null);
log.info("testing:{}",null,null);
log.info("testing",null,null);
output.assertContains("INFO:oejul.LogTest:testing:test,format1");
output.assertContains("INFO:oejul.LogTest:testing:test,format1");
output.assertContains("INFO:oejul.LogTest:testing:test format2");
output.assertContains("INFO:oejul.LogTest:testing test format3");
output.assertContains("INFO:oejul.LogTest:testing:test,null");
output.assertContains("INFO:oejul.LogTest:testing null null");
output.assertContains("INFO:oejul.LogTest:testing:null");
output.assertContains("INFO:oejul.LogTest:testing");
}
@Test
public void testStdErrLogDebug()
{
StdErrLog log = new StdErrLog("xxx");
StdErrCapture output = new StdErrCapture(log);
log.setLevel(StdErrLog.LEVEL_DEBUG);
log.debug("testing {} {}","test","debug");
log.info("testing {} {}","test","info");
log.warn("testing {} {}","test","warn");
log.setLevel(StdErrLog.LEVEL_INFO);
log.debug("YOU SHOULD NOT SEE THIS!",null,null);
// Test for backward compat with old (now deprecated) method
log.setDebugEnabled(true);
log.debug("testing {} {}","test","debug-deprecated");
log.setDebugEnabled(false);
log.debug("testing {} {}","test","debug-deprecated-false");
output.assertContains("DBUG:xxx:testing test debug");
output.assertContains("INFO:xxx:testing test info");
output.assertContains("WARN:xxx:testing test warn");
output.assertNotContains("YOU SHOULD NOT SEE THIS!");
output.assertContains("DBUG:xxx:testing test debug-deprecated");
output.assertNotContains("DBUG:xxx:testing test debug-depdeprecated-false");
}
@Test
public void testStdErrLogName()
{
StdErrLog log = new StdErrLog("test");
log.setPrintLongNames(true);
StdErrCapture output = new StdErrCapture(log);
Assert.assertThat("Log.name", log.getName(), is("test"));
Logger next=log.getLogger("next");
Assert.assertThat("Log.name(child)", next.getName(), is("test.next"));
next.info("testing {} {}","next","info");
output.assertContains(":test.next:testing next info");
}
@Test
public void testStdErrThrowable()
{
// Common Throwable (for test)
Throwable th = new Throwable("Message");
// Capture raw string form
StringWriter tout = new StringWriter();
th.printStackTrace(new PrintWriter(tout));
String ths = tout.toString();
// Start test
StdErrLog log = new StdErrLog("test");
StdErrCapture output = new StdErrCapture(log);
log.warn("ex",th);
output.assertContains(ths);
th = new Throwable("Message with \033 escape");
log.warn("ex",th);
output.assertNotContains("Message with \033 escape");
log.info(th.toString());
output.assertNotContains("Message with \033 escape");
log.warn("ex",th);
output.assertContains("Message with ? escape");
log.info(th.toString());
output.assertContains("Message with ? escape");
}
/** /**
* Test to make sure that using a Null parameter on parameterized messages does not result in a NPE * Test to make sure that using a Null parameter on parameterized messages does not result in a NPE
*/ */
@ -79,6 +179,30 @@ public class StdErrLogTest
Assert.assertEquals("Default Logging Level",StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,StdErrLogTest.class.getName())); Assert.assertEquals("Default Logging Level",StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,StdErrLogTest.class.getName()));
} }
@Test
public void testGetLoggingLevel_Bad()
{
Properties props = new Properties();
props.setProperty("log.LEVEL", "WARN");
props.setProperty("org.eclipse.jetty.bad.LEVEL","FRUIT");
// Default Level (because of bad level value)
Assert.assertEquals("Bad Logging Level",StdErrLog.LEVEL_WARN,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty.bad"));
}
@Test
public void testGetLoggingLevel_Lowercase()
{
Properties props = new Properties();
props.setProperty("log.LEVEL", "warn");
props.setProperty("org.eclipse.jetty.util.LEVEL","info");
// Default Level
Assert.assertEquals("Lowercase Level",StdErrLog.LEVEL_WARN,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty"));
// Specific Level
Assert.assertEquals("Lowercase Level",StdErrLog.LEVEL_INFO,StdErrLog.getLoggingLevel(props,"org.eclipse.jetty.util"));
}
@Test @Test
public void testGetLoggingLevel_Root() public void testGetLoggingLevel_Root()
{ {
@ -158,11 +282,9 @@ public class StdErrLogTest
public void testWarnFiltering() throws UnsupportedEncodingException public void testWarnFiltering() throws UnsupportedEncodingException
{ {
StdErrLog log = new StdErrLog(StdErrLogTest.class.getName()); StdErrLog log = new StdErrLog(StdErrLogTest.class.getName());
log.setHideStacks(true); log.setHideStacks(false);
ByteArrayOutputStream test = new ByteArrayOutputStream(); StdErrCapture output = new StdErrCapture(log);
PrintStream err = new PrintStream(test);
log.setStdErrStream(err);
// Start with default level // Start with default level
log.warn("See Me"); log.warn("See Me");
@ -175,12 +297,19 @@ public class StdErrLogTest
log.setLevel(StdErrLog.LEVEL_WARN); log.setLevel(StdErrLog.LEVEL_WARN);
log.warn("Cheer Me"); log.warn("Cheer Me");
log.warn("<zoom>", new Throwable("out of focus"));
log.warn(new Throwable("scene lost"));
// Validate Output // Validate Output
String output = new String(test.toByteArray(),"UTF-8"); // System.err.print(output);
System.err.print(output); output.assertContains("See Me");
Assert.assertThat(output,containsString("See Me")); output.assertContains("Hear Me");
Assert.assertThat(output,containsString("Hear Me")); output.assertContains("Cheer Me");
Assert.assertThat(output,containsString("Cheer Me"));
// Validate Stack Traces
output.assertContains(".StdErrLogTest:<zoom>");
output.assertContains("java.lang.Throwable: out of focus");
output.assertContains("java.lang.Throwable: scene lost");
} }
/** /**
@ -192,11 +321,9 @@ public class StdErrLogTest
public void testInfoFiltering() throws UnsupportedEncodingException public void testInfoFiltering() throws UnsupportedEncodingException
{ {
StdErrLog log = new StdErrLog(StdErrLogTest.class.getName()); StdErrLog log = new StdErrLog(StdErrLogTest.class.getName());
log.setHideStacks(true); log.setHideStacks(false);
ByteArrayOutputStream test = new ByteArrayOutputStream(); StdErrCapture output = new StdErrCapture(log);
PrintStream err = new PrintStream(test);
log.setStdErrStream(err);
// Normal/Default behavior // Normal/Default behavior
log.info("I will not buy"); log.info("I will not buy");
@ -209,17 +336,27 @@ public class StdErrLogTest
log.setLevel(StdErrLog.LEVEL_ALL); log.setLevel(StdErrLog.LEVEL_ALL);
log.info("it is scratched."); log.info("it is scratched.");
log.info("<zoom>", new Throwable("out of focus"));
log.info(new Throwable("scene lost"));
// Level Warn // Level Warn
log.setLevel(StdErrLog.LEVEL_WARN); log.setLevel(StdErrLog.LEVEL_WARN);
log.info("sorry?"); log.info("sorry?");
log.info("<spoken line>", new Throwable("on editing room floor"));
// Validate Output // Validate Output
String output = new String(test.toByteArray(),"UTF-8"); output.assertContains("I will not buy");
System.err.print(output); output.assertContains("this record");
Assert.assertThat(output,containsString("I will not buy")); output.assertContains("it is scratched.");
Assert.assertThat(output,containsString("this record")); output.assertNotContains("sorry?");
Assert.assertThat(output,containsString("it is scratched."));
Assert.assertThat(output,not(containsString("sorry?"))); // Validate Stack Traces
output.assertNotContains("<spoken line>");
output.assertNotContains("on editing room floor");
output.assertContains(".StdErrLogTest:<zoom>");
output.assertContains("java.lang.Throwable: out of focus");
output.assertContains("java.lang.Throwable: scene lost");
} }
/** /**
@ -233,16 +370,18 @@ public class StdErrLogTest
StdErrLog log = new StdErrLog(StdErrLogTest.class.getName()); StdErrLog log = new StdErrLog(StdErrLogTest.class.getName());
log.setHideStacks(true); log.setHideStacks(true);
ByteArrayOutputStream test = new ByteArrayOutputStream(); StdErrCapture output = new StdErrCapture(log);
PrintStream err = new PrintStream(test);
log.setStdErrStream(err);
// Normal/Default behavior // Normal/Default behavior
log.debug("Tobacconist"); log.debug("Tobacconist");
log.debug("<spoken line>", new Throwable("on editing room floor"));
// Level Debug // Level Debug
log.setLevel(StdErrLog.LEVEL_DEBUG); log.setLevel(StdErrLog.LEVEL_DEBUG);
log.debug("my hovercraft is"); log.debug("my hovercraft is");
log.debug("<zoom>", new Throwable("out of focus"));
log.debug(new Throwable("scene lost"));
// Level All // Level All
log.setLevel(StdErrLog.LEVEL_ALL); log.setLevel(StdErrLog.LEVEL_ALL);
@ -253,12 +392,19 @@ public class StdErrLogTest
log.debug("what?"); log.debug("what?");
// Validate Output // Validate Output
String output = new String(test.toByteArray(),"UTF-8"); // System.err.print(output);
System.err.print(output); output.assertNotContains("Tobacconist");
Assert.assertThat(output,not(containsString("Tobacconist"))); output.assertContains("my hovercraft is");
Assert.assertThat(output,containsString("my hovercraft is")); output.assertContains("full of eels.");
Assert.assertThat(output,containsString("full of eels.")); output.assertNotContains("what?");
Assert.assertThat(output,not(containsString("what?")));
// Validate Stack Traces
output.assertNotContains("<spoken line>");
output.assertNotContains("on editing room floor");
output.assertContains(".StdErrLogTest:<zoom>");
output.assertContains("java.lang.Throwable: out of focus");
output.assertContains("java.lang.Throwable: scene lost");
} }
/** /**
@ -272,9 +418,7 @@ public class StdErrLogTest
StdErrLog log = new StdErrLog(StdErrLogTest.class.getName()); StdErrLog log = new StdErrLog(StdErrLogTest.class.getName());
log.setHideStacks(true); log.setHideStacks(true);
ByteArrayOutputStream test = new ByteArrayOutputStream(); StdErrCapture output = new StdErrCapture(log);
PrintStream err = new PrintStream(test);
log.setStdErrStream(err);
// Normal/Default behavior // Normal/Default behavior
log.ignore(new Throwable("IGNORE ME")); log.ignore(new Throwable("IGNORE ME"));
@ -282,16 +426,222 @@ public class StdErrLogTest
// Show Ignored // Show Ignored
log.setLevel(StdErrLog.LEVEL_ALL); log.setLevel(StdErrLog.LEVEL_ALL);
log.ignore(new Throwable("Don't ignore me")); log.ignore(new Throwable("Don't ignore me"));
// Set to Debug level // Set to Debug level
log.setLevel(StdErrLog.LEVEL_DEBUG); log.setLevel(StdErrLog.LEVEL_DEBUG);
log.ignore(new Throwable("Debug me")); log.ignore(new Throwable("Debug me"));
// Validate Output // Validate Output
// System.err.print(output);
output.assertNotContains("IGNORE ME");
output.assertContains("Don't ignore me");
output.assertNotContains("Debug me");
}
@Test
public void testIsDebugEnabled() {
StdErrLog log = new StdErrLog(StdErrLogTest.class.getName());
log.setHideStacks(true);
log.setLevel(StdErrLog.LEVEL_ALL);
Assert.assertThat("log.level(all).isDebugEnabled", log.isDebugEnabled(), is(true));
log.setLevel(StdErrLog.LEVEL_DEBUG);
Assert.assertThat("log.level(debug).isDebugEnabled", log.isDebugEnabled(), is(true));
log.setLevel(StdErrLog.LEVEL_INFO);
Assert.assertThat("log.level(info).isDebugEnabled", log.isDebugEnabled(), is(false));
log.setLevel(StdErrLog.LEVEL_WARN);
Assert.assertThat("log.level(warn).isDebugEnabled", log.isDebugEnabled(), is(false));
}
@Test
public void testSetGetLevel()
{
StdErrLog log = new StdErrLog(StdErrLogTest.class.getName());
log.setHideStacks(true);
log.setLevel(StdErrLog.LEVEL_ALL);
Assert.assertThat("log.level(all).getLevel()", log.getLevel(), is(StdErrLog.LEVEL_ALL));
log.setLevel(StdErrLog.LEVEL_DEBUG);
Assert.assertThat("log.level(debug).getLevel()", log.getLevel(), is(StdErrLog.LEVEL_DEBUG));
log.setLevel(StdErrLog.LEVEL_INFO);
Assert.assertThat("log.level(info).getLevel()", log.getLevel(), is(StdErrLog.LEVEL_INFO));
log.setLevel(StdErrLog.LEVEL_WARN);
Assert.assertThat("log.level(warn).getLevel()", log.getLevel(), is(StdErrLog.LEVEL_WARN));
}
@Test
public void testGetChildLogger_Simple()
{
String baseName = "jetty";
StdErrLog log = new StdErrLog(baseName);
log.setHideStacks(true);
Assert.assertThat("Logger.name", log.getName(), is("jetty"));
Logger log2 = log.getLogger("child");
Assert.assertThat("Logger.child.name", log2.getName(), is("jetty.child"));
}
@Test
public void testGetChildLogger_Deep()
{
String baseName = "jetty";
StdErrLog log = new StdErrLog(baseName);
log.setHideStacks(true);
Assert.assertThat("Logger.name", log.getName(), is("jetty"));
Logger log2 = log.getLogger("child.of.the.sixties");
Assert.assertThat("Logger.child.name", log2.getName(), is("jetty.child.of.the.sixties"));
}
@Test
public void testGetChildLogger_Null()
{
String baseName = "jetty";
StdErrLog log = new StdErrLog(baseName);
log.setHideStacks(true);
Assert.assertThat("Logger.name", log.getName(), is("jetty"));
// Pass null as child reference, should return parent logger
Logger log2 = log.getLogger(null);
Assert.assertThat("Logger.child.name", log2.getName(), is("jetty"));
Assert.assertSame("Should have returned same logger", log2, log);
}
@Test
public void testGetChildLogger_EmptyName()
{
String baseName = "jetty";
StdErrLog log = new StdErrLog(baseName);
log.setHideStacks(true);
Assert.assertThat("Logger.name", log.getName(), is("jetty"));
// Pass empty name as child reference, should return parent logger
Logger log2 = log.getLogger("");
Assert.assertThat("Logger.child.name", log2.getName(), is("jetty"));
Assert.assertSame("Should have returned same logger", log2, log);
}
@Test
public void testGetChildLogger_EmptyNameSpaces()
{
String baseName = "jetty";
StdErrLog log = new StdErrLog(baseName);
log.setHideStacks(true);
Assert.assertThat("Logger.name", log.getName(), is("jetty"));
// Pass empty name as child reference, should return parent logger
Logger log2 = log.getLogger(" ");
Assert.assertThat("Logger.child.name", log2.getName(), is("jetty"));
Assert.assertSame("Should have returned same logger", log2, log);
}
@Test
public void testGetChildLogger_NullParent()
{
StdErrLog log = new StdErrLog(null);
Assert.assertThat("Logger.name", log.getName(), is(""));
Logger log2 = log.getLogger("jetty");
Assert.assertThat("Logger.child.name", log2.getName(), is("jetty"));
Assert.assertNotSame("Should have returned same logger", log2, log);
}
@Test
public void testToString()
{
StdErrLog log = new StdErrLog("jetty");
log.setLevel(StdErrLog.LEVEL_ALL);
Assert.assertThat("Logger.toString", log.toString(), is("StdErrLog:jetty:LEVEL=ALL"));
log.setLevel(StdErrLog.LEVEL_DEBUG);
Assert.assertThat("Logger.toString", log.toString(), is("StdErrLog:jetty:LEVEL=DEBUG"));
log.setLevel(StdErrLog.LEVEL_INFO);
Assert.assertThat("Logger.toString", log.toString(), is("StdErrLog:jetty:LEVEL=INFO"));
log.setLevel(StdErrLog.LEVEL_WARN);
Assert.assertThat("Logger.toString", log.toString(), is("StdErrLog:jetty:LEVEL=WARN"));
log.setLevel(99); // intentionally bogus level
Assert.assertThat("Logger.toString", log.toString(), is("StdErrLog:jetty:LEVEL=?"));
}
@Test
public void testPrintSource() throws UnsupportedEncodingException
{
StdErrLog log = new StdErrLog("test");
log.setLevel(StdErrLog.LEVEL_DEBUG);
log.setSource(true);
ByteArrayOutputStream test = new ByteArrayOutputStream();
PrintStream err = new PrintStream(test);
log.setStdErrStream(err);
log.debug("Show me the source!");
String output = new String(test.toByteArray(),"UTF-8"); String output = new String(test.toByteArray(),"UTF-8");
System.err.print(output); // System.err.print(output);
Assert.assertThat(output,not(containsString("IGNORE ME")));
Assert.assertThat(output,containsString("Don't ignore me")); Assert.assertThat(output, containsString(".StdErrLogTest#testPrintSource(StdErrLogTest.java:"));
Assert.assertThat(output,not(containsString("Debug me"))); }
@Test
public void testConfiguredAndSetDebugEnabled()
{
Properties props = new Properties();
props.setProperty("org.eclipse.jetty.util.LEVEL","WARN");
props.setProperty("org.eclipse.jetty.io.LEVEL", "WARN");
StdErrLog root = new StdErrLog("", props);
assertLevel(root,StdErrLog.LEVEL_INFO); // default
StdErrLog log = (StdErrLog)root.getLogger(StdErrLogTest.class.getName());
Assert.assertThat("Log.isDebugEnabled()", log.isDebugEnabled(), is(false));
assertLevel(log,StdErrLog.LEVEL_WARN); // as configured
// Boot stomp it all to debug
root.setDebugEnabled(true);
Assert.assertThat("Log.isDebugEnabled()", log.isDebugEnabled(), is(true));
assertLevel(log,StdErrLog.LEVEL_DEBUG); // as stomped
// Restore configured
root.setDebugEnabled(false);
Assert.assertThat("Log.isDebugEnabled()", log.isDebugEnabled(), is(false));
assertLevel(log,StdErrLog.LEVEL_WARN); // as configured
}
private void assertLevel(StdErrLog log, int expectedLevel)
{
Assert.assertThat("Log[" + log.getName() + "].level",levelToString(log.getLevel()),is(levelToString(expectedLevel)));
}
private String levelToString(int level)
{
switch (level)
{
case StdErrLog.LEVEL_ALL:
return "ALL";
case StdErrLog.LEVEL_DEBUG:
return "DEBUG";
case StdErrLog.LEVEL_INFO:
return "INFO";
case StdErrLog.LEVEL_WARN:
return "WARN";
default:
return Integer.toString(level);
}
} }
} }

View File

@ -0,0 +1,2 @@
# Setup default logging implementation for during testing
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog

View File

@ -319,8 +319,15 @@ public class WebSocketClientFactory extends AggregateLifeCycle
}); });
String path=_future.getURI().getPath(); String path=_future.getURI().getPath();
if (path==null || path.length()==0) if (path==null || path.length()==0)
{
path="/"; path="/";
}
if(_future.getURI().getRawQuery() != null)
{
path += "?" + _future.getURI().getRawQuery();
}
String origin = future.getOrigin(); String origin = future.getOrigin();

View File

@ -67,8 +67,6 @@ import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
public class WebSocketConnectionD13 extends AbstractConnection implements WebSocketConnection public class WebSocketConnectionD13 extends AbstractConnection implements WebSocketConnection
{ {
private static final Logger LOG = Log.getLogger(WebSocketConnectionD13.class); private static final Logger LOG = Log.getLogger(WebSocketConnectionD13.class);
private static final boolean STRICT=Boolean.getBoolean("org.eclipse.jetty.websocket.STRICT");
private static final boolean BRUTAL=Boolean.getBoolean("org.eclipse.jetty.websocket.BRUTAL");
final static byte OP_CONTINUATION = 0x00; final static byte OP_CONTINUATION = 0x00;
final static byte OP_TEXT = 0x01; final static byte OP_TEXT = 0x01;
@ -326,7 +324,7 @@ public class WebSocketConnectionD13 extends AbstractConnection implements WebSoc
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public void closeIn(int code,String message) public void closeIn(int code,String message)
{ {
LOG.debug("ClosedIn {} {}",this,message); LOG.debug("ClosedIn {} {} {}",this,code,message);
final boolean closed_out; final boolean closed_out;
final boolean tell_app; final boolean tell_app;
@ -357,7 +355,7 @@ public class WebSocketConnectionD13 extends AbstractConnection implements WebSoc
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public void closeOut(int code,String message) public void closeOut(int code,String message)
{ {
LOG.debug("ClosedOut {} {}",this,message); LOG.debug("ClosedOut {} {} {}",this,code,message);
final boolean closed_out; final boolean closed_out;
final boolean tell_app; final boolean tell_app;
@ -384,9 +382,13 @@ public class WebSocketConnectionD13 extends AbstractConnection implements WebSoc
{ {
if (!closed_out) if (!closed_out)
{ {
if (code<=0) // Close code 1005 (CLOSE No Code) is never to be sent as a status over
// a Close control frame.
if ( (code<=0) || (code == WebSocketConnectionD13.CLOSE_NO_CODE) )
{
code=WebSocketConnectionD13.CLOSE_NORMAL; code=WebSocketConnectionD13.CLOSE_NORMAL;
byte[] bytes = (message==null?"xx":("xx"+message)).getBytes(StringUtil.__ISO_8859_1); }
byte[] bytes = ("xx"+(message==null?"":message)).getBytes(StringUtil.__ISO_8859_1);
bytes[0]=(byte)(code/0x100); bytes[0]=(byte)(code/0x100);
bytes[1]=(byte)(code%0x100); bytes[1]=(byte)(code%0x100);
_outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD13.OP_CLOSE,bytes,0,bytes.length); _outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD13.OP_CLOSE,bytes,0,bytes.length);
@ -456,6 +458,7 @@ public class WebSocketConnectionD13 extends AbstractConnection implements WebSoc
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public void sendControl(byte ctrl, byte[] data, int offset, int length) throws IOException public void sendControl(byte ctrl, byte[] data, int offset, int length) throws IOException
{ {
// TODO: section 5.5 states that control frames MUST never be length > 125 bytes and MUST NOT be fragmented
if (_closedOut) if (_closedOut)
throw new IOException("closedOut "+_closeCode+":"+_closeMessage); throw new IOException("closedOut "+_closeCode+":"+_closeMessage);
_outbound.addFrame((byte)FLAG_FIN,ctrl,data,offset,length); _outbound.addFrame((byte)FLAG_FIN,ctrl,data,offset,length);
@ -630,6 +633,7 @@ public class WebSocketConnectionD13 extends AbstractConnection implements WebSoc
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
private class WSFrameHandler implements WebSocketParser.FrameHandler private class WSFrameHandler implements WebSocketParser.FrameHandler
{ {
private static final int MAX_CONTROL_FRAME_PAYLOAD = 125;
private final Utf8StringBuilder _utf8 = new Utf8StringBuilder(512); // TODO configure initial capacity private final Utf8StringBuilder _utf8 = new Utf8StringBuilder(512); // TODO configure initial capacity
private ByteArrayBuffer _aggregate; private ByteArrayBuffer _aggregate;
private byte _opcode=-1; private byte _opcode=-1;
@ -648,23 +652,23 @@ public class WebSocketConnectionD13 extends AbstractConnection implements WebSoc
{ {
byte[] array=buffer.array(); byte[] array=buffer.array();
if (STRICT) if (isControlFrame(opcode) && buffer.length()>MAX_CONTROL_FRAME_PAYLOAD)
{ {
if (isControlFrame(opcode) && buffer.length()>125) errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Control frame too large: " + buffer.length() + " > " + MAX_CONTROL_FRAME_PAYLOAD);
{ return;
errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Control frame too large"); }
return;
}
if ((flags&0x7)!=0) // TODO: check extensions for RSV bit(s) meanings
{ if ((flags&0x7)!=0)
errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"RSV bits set 0x"+Integer.toHexString(flags)); {
return; errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"RSV bits set 0x"+Integer.toHexString(flags));
} return;
}
// Ignore all frames after error close // Ignore all frames after error close
if (_closeCode!=0 && _closeCode!=CLOSE_NORMAL && opcode!=OP_CLOSE) if (_closeCode!=0 && _closeCode!=CLOSE_NORMAL && opcode!=OP_CLOSE)
return; {
return;
} }
// Deliver frame if websocket is a FrameWebSocket // Deliver frame if websocket is a FrameWebSocket
@ -734,8 +738,10 @@ public class WebSocketConnectionD13 extends AbstractConnection implements WebSoc
case WebSocketConnectionD13.OP_PING: case WebSocketConnectionD13.OP_PING:
{ {
LOG.debug("PING {}",this); LOG.debug("PING {}",this);
if (!_closedOut) if (!_closedOut)
{
_connection.sendControl(WebSocketConnectionD13.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length()); _connection.sendControl(WebSocketConnectionD13.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length());
}
break; break;
} }
@ -752,8 +758,33 @@ public class WebSocketConnectionD13 extends AbstractConnection implements WebSoc
if (buffer.length()>=2) if (buffer.length()>=2)
{ {
code=(0xff&buffer.array()[buffer.getIndex()])*0x100+(0xff&buffer.array()[buffer.getIndex()+1]); code=(0xff&buffer.array()[buffer.getIndex()])*0x100+(0xff&buffer.array()[buffer.getIndex()+1]);
if (buffer.length()>2)
message=new String(buffer.array(),buffer.getIndex()+2,buffer.length()-2,StringUtil.__UTF8); // Validate close status codes.
if (code < WebSocketConnectionD13.CLOSE_NORMAL ||
code == WebSocketConnectionD13.CLOSE_UNDEFINED ||
code == WebSocketConnectionD13.CLOSE_NO_CLOSE ||
code == WebSocketConnectionD13.CLOSE_NO_CODE ||
( code > 1010 && code <= 2999 ) ||
code >= 5000 )
{
errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Invalid close control status code " + code);
return;
}
if (buffer.length()>2)
{
if(_utf8.append(buffer.array(),buffer.getIndex()+2,buffer.length()-2,_connection.getMaxTextMessageSize()))
{
message = _utf8.toString();
_utf8.reset();
}
}
}
else if(buffer.length() == 1)
{
// Invalid length. use status code 1002 (Protocol error)
errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Invalid payload length of 1");
return;
} }
closeIn(code,message); closeIn(code,message);
break; break;
@ -761,7 +792,7 @@ public class WebSocketConnectionD13 extends AbstractConnection implements WebSoc
case WebSocketConnectionD13.OP_TEXT: case WebSocketConnectionD13.OP_TEXT:
{ {
if (STRICT && _opcode!=-1) if (_opcode!=-1)
{ {
errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Expected Continuation"+Integer.toHexString(opcode)); errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Expected Continuation"+Integer.toHexString(opcode));
return; return;
@ -802,7 +833,7 @@ public class WebSocketConnectionD13 extends AbstractConnection implements WebSoc
case WebSocketConnectionD13.OP_BINARY: case WebSocketConnectionD13.OP_BINARY:
{ {
if (STRICT && _opcode!=-1) if (_opcode!=-1)
{ {
errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Expected Continuation"+Integer.toHexString(opcode)); errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Expected Continuation"+Integer.toHexString(opcode));
return; return;
@ -832,8 +863,7 @@ public class WebSocketConnectionD13 extends AbstractConnection implements WebSoc
} }
default: default:
if (STRICT) errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Bad opcode 0x"+Integer.toHexString(opcode));
errorClose(WebSocketConnectionD13.CLOSE_PROTOCOL,"Bad opcode 0x"+Integer.toHexString(opcode));
return; return;
} }
} }
@ -858,17 +888,16 @@ public class WebSocketConnectionD13 extends AbstractConnection implements WebSoc
private void errorClose(int code, String message) private void errorClose(int code, String message)
{ {
_connection.close(code,message); _connection.close(code,message);
if (BRUTAL)
// Brutally drop the connection
try
{ {
try _endp.close();
{ }
_endp.close(); catch (IOException e)
} {
catch (IOException e) LOG.warn(e.toString());
{ LOG.debug(e);
LOG.warn(e.toString());
LOG.debug(e);
}
} }
} }

View File

@ -418,6 +418,7 @@ public class WebSocketClientTest
final AtomicInteger close = new AtomicInteger(); final AtomicInteger close = new AtomicInteger();
final CountDownLatch _latch = new CountDownLatch(1); final CountDownLatch _latch = new CountDownLatch(1);
final BlockingQueue<String> queue = new BlockingArrayQueue<String>(); final BlockingQueue<String> queue = new BlockingArrayQueue<String>();
final StringBuilder closeMessage = new StringBuilder();
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket.OnTextMessage() Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket.OnTextMessage()
{ {
public void onOpen(Connection connection) public void onOpen(Connection connection)
@ -428,6 +429,7 @@ public class WebSocketClientTest
public void onClose(int closeCode, String message) public void onClose(int closeCode, String message)
{ {
close.set(closeCode); close.set(closeCode);
closeMessage.append(message);
_latch.countDown(); _latch.countDown();
} }
@ -476,8 +478,8 @@ public class WebSocketClientTest
_latch.await(10,TimeUnit.SECONDS); _latch.await(10,TimeUnit.SECONDS);
Assert.assertTrue(System.currentTimeMillis()-start<5000); Assert.assertTrue(System.currentTimeMillis()-start<5000);
Assert.assertEquals(1111,close.get()); Assert.assertEquals(1002,close.get());
Assert.assertEquals("Invalid close control status code 1111", closeMessage.toString());
} }
@ -521,6 +523,7 @@ public class WebSocketClientTest
Thread consumer = new Thread() Thread consumer = new Thread()
{ {
@Override
public void run() public void run()
{ {
try try
@ -584,6 +587,7 @@ public class WebSocketClientTest
final AtomicBoolean open = new AtomicBoolean(); final AtomicBoolean open = new AtomicBoolean();
final AtomicInteger close = new AtomicInteger(); final AtomicInteger close = new AtomicInteger();
final CountDownLatch _latch = new CountDownLatch(1); final CountDownLatch _latch = new CountDownLatch(1);
final StringBuilder closeMessage = new StringBuilder();
final Exchanger<String> exchanger = new Exchanger<String>(); final Exchanger<String> exchanger = new Exchanger<String>();
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket.OnTextMessage() Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket.OnTextMessage()
{ {
@ -594,8 +598,8 @@ public class WebSocketClientTest
public void onClose(int closeCode, String message) public void onClose(int closeCode, String message)
{ {
//System.err.println("CLOSE "+closeCode+" "+message);
close.set(closeCode); close.set(closeCode);
closeMessage.append(message);
_latch.countDown(); _latch.countDown();
} }
@ -631,6 +635,7 @@ public class WebSocketClientTest
// Set up a consumer of received messages that waits a while before consuming // Set up a consumer of received messages that waits a while before consuming
Thread consumer = new Thread() Thread consumer = new Thread()
{ {
@Override
public void run() public void run()
{ {
try try
@ -688,8 +693,8 @@ public class WebSocketClientTest
_latch.await(10,TimeUnit.SECONDS); _latch.await(10,TimeUnit.SECONDS);
Assert.assertTrue(System.currentTimeMillis()-start<5000); Assert.assertTrue(System.currentTimeMillis()-start<5000);
Assert.assertEquals(1111,close.get()); Assert.assertEquals(1002,close.get());
Assert.assertEquals("Invalid close control status code 1111", closeMessage.toString());
} }
private void respondToClient(Socket connection, String serverResponse) throws IOException private void respondToClient(Socket connection, String serverResponse) throws IOException

View File

@ -1,8 +1,6 @@
package org.eclipse.jetty.websocket; package org.eclipse.jetty.websocket;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
@ -461,6 +459,7 @@ public class WebSocketMessageD13Test
// unblock the latch in 4s // unblock the latch in 4s
new Thread() new Thread()
{ {
@Override
public void run() public void run()
{ {
try try
@ -544,6 +543,7 @@ public class WebSocketMessageD13Test
final AtomicLong totalB=new AtomicLong(); final AtomicLong totalB=new AtomicLong();
new Thread() new Thread()
{ {
@Override
public void run() public void run()
{ {
try try
@ -950,9 +950,9 @@ public class WebSocketMessageD13Test
output.flush(); output.flush();
assertEquals(0x80|WebSocketConnectionD13.OP_CLOSE,input.read()); assertEquals(0x80|WebSocketConnectionD13.OP_CLOSE,input.read());
assertEquals(2,input.read()); assertEquals(41,input.read());
int code=(0xff&input.read())*0x100+(0xff&input.read()); int code=(0xff&input.read())*0x100+(0xff&input.read());
assertEquals(0x81FF,code); assertEquals(1002,code); // Invalid code 0x81FF
} }
@Test @Test

View File

@ -185,7 +185,7 @@
<plugin> <plugin>
<groupId>org.eclipse.jetty.toolchain</groupId> <groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-version-maven-plugin</artifactId> <artifactId>jetty-version-maven-plugin</artifactId>
<version>1.0.6</version> <version>1.0.7</version>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>

View File

@ -209,5 +209,11 @@
<version>2.1</version> <version>2.1</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
<scope>provided</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -37,14 +37,6 @@
<skip>true</skip> <skip>true</skip>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<!-- DO NOT DEPLOY (or Release) -->
<skip>true</skip>
</configuration>
</plugin>
</plugins> </plugins>
</build> </build>
<modules> <modules>

View File

@ -26,15 +26,23 @@
<artifactId>test-hash-sessions</artifactId> <artifactId>test-hash-sessions</artifactId>
<name>Jetty Tests :: Sessions :: Hash</name> <name>Jetty Tests :: Sessions :: Hash</name>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<configuration> <configuration>
<source>1.5</source> <source>1.5</source>
<target>1.5</target> <target>1.5</target>
</configuration> </configuration>
</plugin> </plugin>
</plugins> <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<!-- DO NOT DEPLOY (or Release) -->
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build> </build>
<dependencies> <dependencies>
<dependency> <dependency>

View File

@ -26,15 +26,23 @@
<artifactId>test-jdbc-sessions</artifactId> <artifactId>test-jdbc-sessions</artifactId>
<name>Jetty Tests :: Sessions :: JDBC</name> <name>Jetty Tests :: Sessions :: JDBC</name>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<configuration> <configuration>
<source>1.5</source> <source>1.5</source>
<target>1.5</target> <target>1.5</target>
</configuration> </configuration>
</plugin> </plugin>
</plugins> <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<!-- DO NOT DEPLOY (or Release) -->
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build> </build>
<dependencies> <dependencies>
<dependency> <dependency>

View File

@ -27,6 +27,16 @@
<name>Jetty Tests :: WebApps :: Parent</name> <name>Jetty Tests :: WebApps :: Parent</name>
<packaging>pom</packaging> <packaging>pom</packaging>
<build> <build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<!-- DO NOT DEPLOY (or Release) -->
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build> </build>
<modules> <modules>
<module>test-webapp-rfc2616</module> <module>test-webapp-rfc2616</module>

View File

@ -27,6 +27,16 @@
<name>Jetty Tests :: WebApp :: RFC2616</name> <name>Jetty Tests :: WebApp :: RFC2616</name>
<packaging>war</packaging> <packaging>war</packaging>
<build> <build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<!-- DO NOT DEPLOY (or Release) -->
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build> </build>
<dependencies> <dependencies>
<dependency> <dependency>