Merged branch 'jetty-9.4.x' into 'master'.

This commit is contained in:
Simone Bordet 2017-05-02 10:03:30 +02:00
commit cab1ec77ad
18 changed files with 814 additions and 357 deletions

View File

@ -17,93 +17,93 @@
[[using-jmx]] [[using-jmx]]
=== Using JMX with Jetty === Using JMX with Jetty
Jetty JMX integration uses the platform MBean server implementation that Java VM provides. Jetty's architecture is based on POJO components (see xref:basic-architecture[]).
The integration is based on the `ObjectMBean` implementation of `DynamicMBean`. These components are organized in a tree and each component may have a lifecycle
This implementation allows you to wrap an arbitrary POJO in an MBean and annotate it appropriately to expose it via JMX. that spans the `Server` lifetime, or a web application lifetime, or even shorter
See xref:jetty-jmx-annotations[]. lifetimes such as that of a TCP connection.
The `MBeanContainer` implementation of the `Container.Listener` interface coordinates creation of MBeans. Every time a component is added or removed from the component tree, an event is
The Jetty Server and it's components use a link:{JDURL}/org/eclipse/jetty/util/component/Container.html[Container] to maintain a containment tree of components and to support notification of changes to that tree. emitted, and link:{JDURL}/org/eclipse/jetty/util/component/Container.html[`Container.Listener`]
The `MBeanContainer` class listens for Container events and creates and destroys MBeans as required to wrap all Jetty components. implementations can listen to those events and perform additional actions.
You can access the MBeans that Jetty publishes both through built-in Java VM connector via JConsole or JMC, or by registering a remote JMX connector and using a remote JMX agent to monitor Jetty. One such `Container.Listener` is `MBeanContainer` that uses `ObjectMBean` to
create an MBean from an arbitrary POJO, and register/unregister the MBean to/from
the platform `MBeanServer`.
Jetty components are annotated with <<jetty-jmx-annotations,JMX annotations>>
and provide specific JMX details so that `ObjectMBean` can build a more
precise representation of the JMX metadata associated with the component POJO.
Therefore, when a component is added to the component tree, `MBeanContainer` is
notified, it creates the MBean from the component POJO and registers it to
the `MBeanServer`.
Similarly, when a component is removed from the tree, `MBeanContainer` is
notified, and unregisters the MBean from the `MBeanServer`.
The Jetty MBeans can be accessed via any JMX console such as Java Mission Control
(JMC), VisualVM, JConsole or others.
[[configuring-jmx]] [[configuring-jmx]]
==== Configuring JMX ==== Configuring JMX
This guide describes how to initialize and configure the Jetty JMX integration. This guide describes the various ways to initialize and configure the Jetty JMX integration.
Configuring the Jetty JMX integration only registers the Jetty MBeans into the platform
`MBeanServer`, and therefore the MBeans can only be accessed locally (from the same machine),
not from remote machines.
To monitor an application using JMX, perform the following steps: This means that this configuration is enough for development, where you have easy access
(with graphical user interface) to the machine where Jetty runs, but it is typically not
* Configure the application to instantiate an MBean container. enough when the machine Jetty where runs is remote, or only accessible via SSH or otherwise
* Instrument objects to be MBeans. without graphical user interface support.
* Provide access for JMX agents to MBeans. In these cases, you have to enable <<jmx-remote-access,JMX remote access>>.
[[accessing-jetty-mbeans]]
===== Using JConsole to Access Jetty MBeans
The simplest way to access the MBeans that Jetty publishes is to use the http://java.sun.com/developer/technicalArticles/J2SE/jconsole.html[JConsole utility] the Java Virtual Machine supplies.
See xref:jetty-jconsole[] for instructions on how to configure JVM for use with JConsole or JMC.
To access Jetty MBeans via JConsole or JMC, you must:
* Enable the registration of Jetty MBeans into the platform MBeanServer.
* Enable a `JMXConnectorServer` so that JConsole/JMC can connect and visualize the MBeans.
[[registering-jetty-mbeans]]
===== Registering Jetty MBeans
Configuring Jetty JMX integration differs for standalone and embedded Jetty.
[[jmx-standalone-jetty]] [[jmx-standalone-jetty]]
====== Standalone Jetty ===== Standalone Jetty Server
JMX is not enabled by default in the Jetty distribution. JMX is not enabled by default in the Jetty distribution.
To enable JMX in the Jetty distribution, run the following, where `{$jetty.home}` is the directory where you have the Jetty distribution located (see link:#startup-base-and-home[the documentation for Jetty base vs. home examples]): To enable JMX in the Jetty distribution run the following, where `{$jetty.home}`
is the directory where you have the Jetty distribution installed, and
`${jetty.base}` is the directory where you have your Jetty configuration
(see link:#startup-base-and-home[the documentation for Jetty base vs. home examples]):
[source, screen, subs="{sub-order}"] [source, screen, subs="{sub-order}"]
.... ----
$ cd ${jetty.base}
$ java -jar {$jetty.home}/start.jar --add-to-start=jmx $ java -jar {$jetty.home}/start.jar --add-to-start=jmx
.... ----
Running the above command will append the available configurable elements of the JMX module to the `{$jetty.base}/start.ini` file. Running the above command will append the available configurable elements of the `jmx` module
If you are managing separate ini files for your modules in the distribution, use `--add-to-start.d=jmx` instead. to the `{$jetty.base}/start.ini` file, or create the `${jetty.base}/start.d/jmx.ini` file.
If you wish to add remote access for JMX, you will also need to enable the JMX-Remote module:
[source, screen, subs="{sub-order}"]
....
$ java -jar {$jetty.home}/start.jar --add-to-start=jmx-remote
....
[[jmx-embedded-jetty]] [[jmx-embedded-jetty]]
====== Embedded Jetty ===== Embedded Jetty Server
When running Jetty embedded into an application, create and configure an MBeanContainer instance as follows: When running Jetty embedded into an application, create and configure an `MBeanContainer`
instance as follows:
[source, java] [source, java]
---- ----
Server server = new Server(); Server server = new Server();
// Setup JMX // Setup JMX.
MBeanContainer mbContainer=new MBeanContainer(ManagementFactory.getPlatformMBeanServer()); MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
server.addEventListener(mbContainer); server.addBean(mbeanContainer);
server.addBean(mbContainer);
// Add loggers MBean to server (will be picked up by MBeanContainer above) // Export the loggers as MBeans.
server.addBean(Log.getLog()); server.addBean(Log.getLog());
---- ----
Notice that Jetty creates the `MBeanContainer` immediately after creating the Server, and immediately after registering it as an `EventListener` of the Server object (which is also a Container object). Because logging is initialized prior to the `MBeanContainer` (even before the `Server` itself),
it is necessary to register the logger manually via `server.addBean()` so that the loggers may
show up in the JMX tree as MBeans.
Because logging is initialized prior to the `MBeanContainer` (even before the Server itself), it is necessary to register the logger manually via `server.addBean()` so that the loggers may show up in the JMX tree. [[jmx-jetty-maven-plugin]]
[[jmx-using-jetty-maven-plugin]]
===== Using the Jetty Maven Plugin with JMX ===== Using the Jetty Maven Plugin with JMX
If you are using the link:#jetty-maven-plugin[Jetty Maven plugin] you should copy the `/etc/jetty-jmx.xml` file into your webapp project somewhere, such as `/src/etc,` then add a `<jettyconfig>` element to the plugin `<configuration>`: If you are using the link:#jetty-maven-plugin[Jetty Maven plugin] you should copy the
`${jetty.home}/etc/jetty-jmx.xml` file into your webapp project somewhere, such as
`src/main/config/etc/`, then add a
`<jettyXml>` element to the `<configuration>` element of the Jetty Maven Plugin:
[source, xml, subs="{sub-order}"] [source, xml, subs="{sub-order}"]
---- ----
@ -113,21 +113,153 @@ If you are using the link:#jetty-maven-plugin[Jetty Maven plugin] you should cop
<version>{VERSION}</version> <version>{VERSION}</version>
<configuration> <configuration>
<scanintervalseconds>10</scanintervalseconds> <scanintervalseconds>10</scanintervalseconds>
<jettyXml>src/etc/jetty-jmx.xml</jettyXml> <jettyXml>src/main/config/etc/jetty-jmx.xml</jettyXml>
</configuration> </configuration>
</plugin> </plugin>
---- ----
[[accessing-jetty-mbeans]]
==== Using JConsole or Java Mission Control to Access Jetty MBeans
[[enabling-jmxconnectorserver-for-remote-access]] The simplest way to access the MBeans that Jetty publishes is to use
==== Enabling JMXConnectorServer for Remote Access <<jetty-jconsole,Java Mission Control (JMC) or JConsole>>.
There are two ways of enabling remote connectivity so that JConsole or JMC can connect to visualize MBeans. Both these tools can connect to local or remote JVMs to display the MBeans.
For local access, you just need to start JConsole or JMC and then choose
from their user interface the local JVM you want to connect to.
For remote access, you need first to enable <<jmx-remote-access,JMX remote access>>
in Jetty.
[[jmx-remote-access]]
==== Enabling JMX Remote Access
There are two ways of enabling remote connectivity so that JConsole or JMC can connect
to the remote JVM to visualize MBeans.
* Use the `com.sun.management.jmxremote` system property on the command line. * Use the `com.sun.management.jmxremote` system property on the command line.
Unfortunately, this solution does not work well with firewalls and is not flexible. Unfortunately, this solution does not work well with firewalls and is not flexible.
* Use Jetty's `ConnectorServer` class. * Use Jetty's `jmx-remote` module or - equivalently - the `ConnectorServer` class.
To enable use of this class, uncomment the correspondent portion in `/etc/jetty-jmx.xml,` like this:
`ConnectorServer` will use by default RMI to allow connection from remote clients,
and it is a wrapper around the standard JDK class `JMXConnectorServer`, which is
the class that provides remote access to JMX clients.
Connecting to the remote JVM is a two step process:
* First, the client will connect to the RMI _registry_ to download the RMI stub for
the `JMXConnectorServer`; this RMI stub contains the IP address and port to connect
to the RMI server, i.e. the remote `JMXConnectorServer`.
* Second, the client uses the RMI stub to connect to the RMI _server_ (i.e. the
remote `JMXConnectorServer`) typically on an address and port that may be different
from the RMI registry address and port.
The configuration for the RMI registry and the RMI server is specified by a `JMXServiceURL`.
The string format of an RMI `JMXServiceURL` is:
[source, screen, subs="{sub-order}"]
----
service:jmx:rmi://<rmi_server_host>:<rmi_server_port>/jndi/rmi://<rmi_registry_host>:<rmi_registry_port>/jmxrmi
----
Default values are:
[source, screen, subs="{sub-order}"]
----
rmi_server_host = localhost
rmi_server_port = 1099
rmi_registry_host = localhost
rmi_registry_port = 1099
----
With the default configuration, only clients that are local to the server machine can connect
to the RMI registry and RMI server - this is done for security reasons.
With this configuration it would still be possible to access the MBeans from remote using
a <<jmx-remote-access-ssh-tunnel,SSH tunnel>>.
By specifying an appropriate `JMXServiceURL`, you can fine tune the network interfaces the
RMI registry and the RMI server bind to, and the ports that the RMI registry and the RMI server
listen to.
The RMI server and RMI registry hosts and ports can be the same (as in the default configuration)
because RMI is able to multiplex traffic arriving to a port to multiple RMI objects.
If you need to allow JMX remote access through a firewall, you must open both the RMI registry
and the RMI server ports.
Examples:
[source, screen, subs="{sub-order}"]
----
service:jmx:rmi:///jndi/rmi:///jmxrmi
rmi_server_host = any address
rmi_server_port = randomly chosen
rmi_registry_host = any address
rmi_registry_port = 1099
service:jmx:rmi://localhost:1100/jndi/rmi://localhost:1099/jmxrmi
rmi_server_host = loopback address
rmi_server_port = 1100
rmi_registry_host = loopback address
rmi_registry_port = 1099
----
[NOTE]
====
When `ConnectorServer` is started, its RMI stub is exported to the RMI registry.
The RMI stub contains the IP address and port to connect to the RMI object, but
the IP address is typically the machine host name, not the host specified in the
`JMXServiceURL`.
To control the IP address stored in the RMI stub you need to set the system
property `java.rmi.server.hostname` with the desired value.
This is especially important when binding the RMI server host to the loopback
address for security reasons. See also
<<jmx-remote-access-ssh-tunnel,JMX Remote Access via SSH Tunnel>>.
====
===== Enabling JMX Remote Access in Standalone Jetty Server
Similarly to <<jmx-standalone-jetty,enabling JMX in a standalone Jetty server>>, you
enable the `jmx-remote` module:
[source, screen, subs="{sub-order}"]
----
$ cd ${jetty.base}
$ java -jar {$jetty.home}/start.jar --add-to-start=jmx-remote
----
===== Enabling JMX Remote Access in Embedded Jetty
When running Jetty embedded into an application, create and configure a `ConnectorServer`:
[source, java, subs="{sub-order}"]
----
Server server = new Server();
// Setup JMX
MBeanContainer mbeanContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
server.addBean(mbeanContainer);
// Setup ConnectorServer
JMXServiceURL jmxURL = new JMXServiceURL("rmi", null, 1999, "/jndi/rmi:///jmxrmi");
ConnectorServer jmxServer = new ConnectorServer(jmxURL, "org.eclipse.jetty.jmx:name=rmiconnectorserver");
server.addBean(jmxServer);
----
The `JMXServiceURL` above specifies that the RMI server binds to the wildcard address
on port 1999, while the RMI registry binds to the wildcard address on port 1099 (the
default RMI registry port).
[[jmx-remote-access-authorization]]
===== JMX Remote Access Authorization
The standard `JMXConnectorServer` provides several options to authorize access.
For a complete guide to controlling authentication and authorization in JMX, see
https://blogs.oracle.com/lmalventosa/entry/jmx_authentication_authorization[Authentication and Authorization in JMX RMI connectors].
To authorize access to the `JMXConnectorServer` you can use this configuration,
where the `jmx.password` and `jmx.access` files have the format specified in the blog entry above:
[source, xml, subs="{sub-order}"] [source, xml, subs="{sub-order}"]
---- ----
@ -136,50 +268,22 @@ To enable use of this class, uncomment the correspondent portion in `/etc/jetty-
<New class="javax.management.remote.JMXServiceURL"> <New class="javax.management.remote.JMXServiceURL">
<Arg type="java.lang.String">rmi</Arg> <Arg type="java.lang.String">rmi</Arg>
<Arg type="java.lang.String" /> <Arg type="java.lang.String" />
<Arg type="java.lang.Integer"><SystemProperty name="jetty.jmxrmiport" default="1099"/></Arg> <Arg type="java.lang.Integer">1099</Arg>
<Arg type="java.lang.String">/jndi/rmi://<SystemProperty name="jetty.jmxrmihost" default="localhost"/>:<SystemProperty name="jetty.jmxrmiport" default="1099"/>/jmxrmi</Arg> <Arg type="java.lang.String">/jndi/rmi:///jmxrmi</Arg>
</New>
</Arg>
<Arg>org.eclipse.jetty.jmx:name=rmiconnectorserver</Arg>
<Call name="start" />
</New>
----
This configuration snippet starts an `RMIRegistry` and a `JMXConnectorServer` both on port 1099 (by default), so that firewalls should open just that one port to allow connections from JConsole or JMC.
[[securing-remote-access]]
==== Securing Remote Access
`JMXConnectorServer` several options to restrict access.
For a complete guide to controlling authentication and authorization in JMX, see https://blogs.oracle.com/lmalventosa/entry/jmx_authentication_authorization[Authentication and Authorization in JMX RMI connectors] in Luis-Miguel Alventosa's blog.
To restrict access to the `JMXConnectorServer`, you can use this configuration, where the `jmx.password` and `jmx.access` files have the format specified in the blog entry above:
[source, xml, subs="{sub-order}"]
----
<New id="ConnectorServer" class="org.eclipse.jetty.jmx.ConnectorServer">
<Arg>
<New class="javax.management.remote.JMXServiceURL">
<Arg type="java.lang.String">rmi</Arg>
<Arg type="java.lang.String" />
<Arg type="java.lang.Integer"><SystemProperty name="jetty.jmxrmiport" default="1099"/></Arg>
<Arg type="java.lang.String">/jndi/rmi://<SystemProperty name="jetty.jmxrmihost" default="localhost"/>:<SystemProperty name="jetty.jmxrmiport" default="1099"/>/jmxrmi</Arg>
</New> </New>
</Arg> </Arg>
<Arg> <Arg>
<Map> <Map>
<Entry> <Entry>
<Item>jmx.remote.x.password.file</Item> <Item>jmx.remote.x.access.file</Item>
<Item> <Item>
<New class="java.lang.String"><Arg><Property name="jetty.home" default="." />/resources/jmx.password</Arg></New> <New class="java.lang.String"><Arg><Property name="jetty.base" default="." />/resources/jmx.access</Arg></New>
</Item> </Item>
</Entry> </Entry>
<Entry> <Entry>
<Item>jmx.remote.x.access.file</Item> <Item>jmx.remote.x.password.file</Item>
<Item> <Item>
<New class="java.lang.String"><Arg><Property name="jetty.home" default="." />/resources/jmx.access</Arg></New> <New class="java.lang.String"><Arg><Property name="jetty.base" default="." />/resources/jmx.password</Arg></New>
</Item> </Item>
</Entry> </Entry>
</Map> </Map>
@ -187,17 +291,141 @@ To restrict access to the `JMXConnectorServer`, you can use this configuration,
<Arg>org.eclipse.jetty.jmx:name=rmiconnectorserver</Arg> <Arg>org.eclipse.jetty.jmx:name=rmiconnectorserver</Arg>
<Call name="start" /> <Call name="start" />
</New> </New>
---- ----
[[custom-monitor-applcation]] Similarly, in code:
==== Custom Monitor Application
Using the JMX API, you can also write a custom application to monitor your Jetty server. [source, java, subs="{sub-order}"]
To allow this application to connect to your Jetty server, you need to uncomment the last section of the `/etc/jetty-jmx.xml` configuration file and optionally modify the endpoint name. ----
Doing so creates a JMX HTTP connector and registers a JMX URL that outputs to the `Stderr` log. JMXServiceURL jmxURL = new JMXServiceURL("rmi", null, 1099, "/jndi/rmi:///jmxrmi");
Map<String, Object> env = new HashMap<>();
env.put("jmx.remote.x.access.file", "resources/jmx.access");
env.put("jmx.remote.x.password.file", "resources/jmx.password");
ConnectorServer jmxServer = new ConnectorServer(jmxURL, env, "org.eclipse.jetty.jmx:name=rmiconnectorserver");
jmxServer.start();
----
You should provide the URL that appears in the log to your monitor application in order to create an `MBeanServerConnection.` Calling `ConnectorServer.start()` may be explicit as in the examples above,
You can use the same URL to connect to your Jetty instance from a remote machine using JConsole or JMC. or can be skipped when adding the `ConnectorServer` as a bean to the `Server`,
See the link:{GITBROWSEURL}/jetty-jmx/src/main/config/etc/jetty-jmx.xml[configuration file] for more details. so that starting the `Server` will also start the `ConnectorServer`.
===== Securing JMX Remote Access with TLS
The JMX communication via RMI happens by default in clear-text.
It is possible to configure the `ConnectorServer` with a `SslContextFactory` so
that the JMX communication via RMI is encrypted:
[source, xml, subs="{sub-order}"]
----
<New id="ConnectorServer" class="org.eclipse.jetty.jmx.ConnectorServer">
<Arg>
<New class="javax.management.remote.JMXServiceURL">
<Arg type="java.lang.String">rmi</Arg>
<Arg type="java.lang.String" />
<Arg type="java.lang.Integer">1099</Arg>
<Arg type="java.lang.String">/jndi/rmi:///jmxrmi</Arg>
</New>
</Arg>
<Arg />
<Arg>org.eclipse.jetty.jmx:name=rmiconnectorserver</Arg>
<Arg><Ref refid="sslContextFactory" /></Arg>
</New>
----
Similarly, in code:
[source, java, subs="{sub-order}"]
----
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath();
sslContextFactory.setKeyStorePassword("secret");
JMXServiceURL jmxURL = new JMXServiceURL("rmi", null, 1099, "/jndi/rmi:///jmxrmi");
ConnectorServer jmxServer = new ConnectorServer(jmxURL, null, "org.eclipse.jetty.jmx:name=rmiconnectorserver", sslContextFactory);
----
It is possible to use the same `SslContextFactory` used to configure the
Jetty `ServerConnector` that supports TLS for the HTTP protocol.
This is used in the XML example above: the `SslContextFactory` configured
for the TLS `ServerConnector` is registered with an id of `sslContextFactory`
which is referenced in the XML via the `Ref` element.
The keystore must contain a valid certificate signed by a Certification Authority.
The RMI mechanic is the usual one: the RMI client (typically a monitoring console)
will connect first to the RMI registry (using TLS), download the RMI server stub
that contains the address and port of the RMI server to connect to, then connect
to the RMI server (using TLS).
This also mean that if the RMI registry and the RMI server are on different hosts,
the RMI client must have available the cryptographic material to validate both
hosts.
Having certificates signed by a Certification Authority simplifies by a lot the
configuration needed to get the JMX communication over TLS working properly.
If that is not the case (for example the certificate is self-signed), then you
need to specify the required system properties that allow RMI (especially when
acting as an RMI client) to retrieve the cryptographic material necessary to
establish the TLS connection.
For example, trying to connect using the JDK standard `JMXConnector` with both
the RMI server and the RMI registry to `domain.com`:
[source, java, subs="{sub-order}"]
----
// System properties necessary for an RMI client to trust a self-signed certificate.
System.setProperty("javax.net.ssl.trustStore", "/path/to/trustStore");
System.setProperty("javax.net.ssl.trustStorePassword", "secret");
JMXServiceURL jmxURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://domain.com:1100/jmxrmi")
Map<String, Object> clientEnv = new HashMap<>();
// Required to connect to the RMI registry via TLS.
clientEnv.put(ConnectorServer.RMI_REGISTRY_CLIENT_SOCKET_FACTORY_ATTRIBUTE, new SslRMIClientSocketFactory());
try (JMXConnector client = JMXConnectorFactory.connect(jmxURL, clientEnv))
{
Set<ObjectName> names = client.getMBeanServerConnection().queryNames(null, null);
}
----
Similarly, to launch JMC:
[source, java, subs="{sub-order}"]
----
$ jmc -vmargs -Djavax.net.ssl.trustStore=/path/to/trustStore -Djavax.net.ssl.trustStorePassword=secret
----
Note that these system properties are required when launching the `ConnectorServer` too,
on the server, because it acts as an RMI client with respect to the RMI registry.
[[jmx-remote-access-ssh-tunnel]]
===== JMX Remote Access with Port Forwarding via SSH Tunnel
You can access JMX MBeans on a remote machine when the RMI ports are not open,
for example because of firewall policies, but you have SSH access to the machine
using local port forwarding via a SSH tunnel.
In this case you want to configure the `ConnectorServer` with a `JMXServiceURL`
that binds the RMI server and the RMI registry to the loopback interface only:
`service:jmx:rmi://localhost:1099/jndi/rmi://localhost:1099/jmxrmi`.
Then you setup the local port forwarding with the SSH tunnel:
[source, screen, subs="{sub-order}"]
----
$ ssh -L 1099:localhost:1099 <user>@<machine_host>
----
Now you can use JConsole or JMC to connect to `localhost:1099` on your local
computer. The traffic will be forwarded to `machine_host` and when there,
SSH will forward the traffic to `localhost:1099`, which is exactly where
the `ConnectorServer` listens.
When you configure `ConnectorServer` in this way, you must set the system
property `-Djava.rmi.server.hostname=localhost`, on the server.
This is required because when the RMI server is exported, its address and
port are stored in the RMI stub. You want the address in the RMI stub to be
`localhost` so that when the RMI stub is downloaded to the remote client,
the RMI communication will go through the SSH tunnel.

View File

@ -13,14 +13,14 @@
</Call> </Call>
--> -->
<!-- Add a remote JMX connector. The parameters of the constructor <!-- Adds a remote JMXConnectorServer. The parameters of the constructor
below specify the JMX service URL, and the object name string for the below specify the JMXServiceURL, and the ObjectName string for the
connector server bean. The parameters of the JMXServiceURL constructor JMXConnectorServer. The parameters of the JMXServiceURL constructor
specify the protocol that clients will use to connect to the remote JMX specify the protocol that clients will use to connect to the remote JMX
connector (RMI), the hostname of the server (local hostname), port number connector (rmi), the hostname and port number of the RMI server, and the
(automatically assigned), and the URL path. Note that URL path contains URL path. Note that URL path contains the RMI registry hostname and port
the RMI registry hostname and port number, that may need to be modified number. Modify the port numbers if you need to comply with the firewall
in order to comply with the firewall requirements. requirements.
--> -->
<Call name="addBean"> <Call name="addBean">
<Arg> <Arg>
@ -28,9 +28,9 @@
<Arg> <Arg>
<New class="javax.management.remote.JMXServiceURL"> <New class="javax.management.remote.JMXServiceURL">
<Arg type="java.lang.String">rmi</Arg> <Arg type="java.lang.String">rmi</Arg>
<Arg type="java.lang.String"><Property name="jetty.jmxremote.rmihost" deprecated="jetty.jmxrmihost" default="localhost"/></Arg> <Arg type="java.lang.String"><Property name="jetty.jmxremote.rmiserverhost" deprecated="jetty.jmxremote.rmihost,jetty.jmxrmihost" default="localhost"/></Arg>
<Arg type="java.lang.Integer"><Property name="jetty.jmxremote.rmiport" deprecated="jetty.jmxrmiport" default="1099"/></Arg> <Arg type="java.lang.Integer"><Property name="jetty.jmxremote.rmiserverport" deprecated="jetty.jmxremote.rmiport,jetty.jmxrmiport" default="1099"/></Arg>
<Arg type="java.lang.String">/jndi/rmi://<Property name="jetty.jmxremote.rmihost" deprecated="jetty.jmxrmihost" default="localhost"/>:<Property name="jetty.jmxremote.rmiport" deprecated="jetty.jmxrmiport" default="1099"/>/jmxrmi</Arg> <Arg type="java.lang.String">/jndi/rmi://<Property name="jetty.jmxremote.rmiregistryhost" deprecated="jetty.jmxremote.rmihost,jetty.jmxrmihost" default="localhost"/>:<Property name="jetty.jmxremote.rmiregistryport" deprecated="jetty.jmxremote.rmiport,jetty.jmxrmiport" default="1099"/>/jmxrmi</Arg>
</New> </New>
</Arg> </Arg>
<Arg>org.eclipse.jetty.jmx:name=rmiconnectorserver</Arg> <Arg>org.eclipse.jetty.jmx:name=rmiconnectorserver</Arg>

View File

@ -4,13 +4,13 @@
<Configure id="Server" class="org.eclipse.jetty.server.Server"> <Configure id="Server" class="org.eclipse.jetty.server.Server">
<!-- =========================================================== --> <!-- =========================================================== -->
<!-- Get the platform mbean server --> <!-- Get the platform MBeanServer -->
<!-- =========================================================== --> <!-- =========================================================== -->
<Call id="MBeanServer" class="java.lang.management.ManagementFactory" <Call id="MBeanServer" class="java.lang.management.ManagementFactory"
name="getPlatformMBeanServer" /> name="getPlatformMBeanServer" />
<!-- =========================================================== --> <!-- =========================================================== -->
<!-- Initialize the Jetty MBean container --> <!-- Initialize the Jetty MBeanContainer -->
<!-- =========================================================== --> <!-- =========================================================== -->
<Call name="addBean"> <Call name="addBean">
<Arg> <Arg>
@ -25,7 +25,7 @@
<!-- Add the static log --> <!-- Add the static log -->
<Call name="addBean"> <Call name="addBean">
<Arg> <Arg>
<New class="org.eclipse.jetty.util.log.Log" /> <Get class="org.eclipse.jetty.util.log.Log" name="Log" />
</Arg> </Arg>
</Call> </Call>
</Configure> </Configure>

View File

@ -8,8 +8,14 @@ jmx
etc/jetty-jmx-remote.xml etc/jetty-jmx-remote.xml
[ini-template] [ini-template]
## The host/address to bind RMI to ## The host/address to bind the RMI server to.
# jetty.jmxremote.rmihost=localhost # jetty.jmxremote.rmiserverhost=localhost
## The port RMI listens to ## The port the RMI server listens to (0 means a random port is chosen).
# jetty.jmxremote.rmiport=1099 # jetty.jmxremote.rmiserverport=1099
## The host/address to bind the RMI registry to.
# jetty.jmxremote.rmiregistryhost=localhost
## The port the RMI registry listens to.
# jetty.jmxremote.rmiregistryport=1099

View File

@ -18,160 +18,185 @@
package org.eclipse.jetty.jmx; package org.eclipse.jetty.jmx;
import java.io.IOException;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.UnknownHostException;
import java.rmi.registry.LocateRegistry; import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry; import java.rmi.registry.Registry;
import java.rmi.server.RMIClientSocketFactory;
import java.rmi.server.RMIServerSocketFactory;
import java.rmi.server.UnicastRemoteObject; import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.function.IntConsumer;
import javax.management.MBeanServer; import javax.management.MBeanServer;
import javax.management.ObjectName; import javax.management.ObjectName;
import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL; import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnectorServer;
import javax.rmi.ssl.SslRMIClientSocketFactory;
import org.eclipse.jetty.util.HostPort; import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.ShutdownThread; import org.eclipse.jetty.util.thread.ShutdownThread;
/* ------------------------------------------------------------ */
/** /**
* AbstractLifeCycle wrapper for JMXConnector Server * <p>LifeCycle wrapper for JMXConnectorServer.</p>
* <p>This class provides the following facilities:</p>
* <ul>
* <li>participates in the {@code Server} lifecycle</li>
* <li>starts the RMI registry if not there already</li>
* <li>allows to bind the RMI registry and the RMI server to the loopback interface</li>
* <li>makes it easy to use TLS for the JMX communication</li>
* </ul>
*/ */
public class ConnectorServer extends AbstractLifeCycle public class ConnectorServer extends AbstractLifeCycle
{ {
public static final String RMI_REGISTRY_CLIENT_SOCKET_FACTORY_ATTRIBUTE = "com.sun.jndi.rmi.factory.socket";
private static final Logger LOG = Log.getLogger(ConnectorServer.class); private static final Logger LOG = Log.getLogger(ConnectorServer.class);
JMXConnectorServer _connectorServer; private JMXServiceURL _jmxURL;
Registry _registry; private final Map<String, Object> _environment;
private final String _objectName;
private final SslContextFactory _sslContextFactory;
private int _registryPort;
private int _rmiPort;
private JMXConnectorServer _connectorServer;
private Registry _registry;
/* ------------------------------------------------------------ */
/** /**
* Constructs connector server * Constructs a ConnectorServer
* *
* @param serviceURL the address of the new connector server. * @param serviceURL the address of the new ConnectorServer
* The actual address of the new connector server, as returned * @param name object name string to be assigned to ConnectorServer bean
* by its getAddress method, will not necessarily be exactly the same.
* @param name object name string to be assigned to connector server bean
* @throws Exception if unable to setup connector server
*/ */
public ConnectorServer(JMXServiceURL serviceURL, String name) public ConnectorServer(JMXServiceURL serviceURL, String name)
throws Exception
{ {
this(serviceURL, null, name); this(serviceURL, null, name);
} }
/* ------------------------------------------------------------ */
/** /**
* Constructs connector server * Constructs a ConnectorServer
* *
* @param svcUrl the address of the new connector server. * @param svcUrl the address of the new ConnectorServer
* The actual address of the new connector server, as returned * @param environment a set of attributes to control the new ConnectorServer's behavior.
* by its getAddress method, will not necessarily be exactly the same. * This parameter can be null. Keys in this map must
* @param environment a set of attributes to control the new connector * be Strings. The appropriate type of each associated value depends on
* server's behavior. This parameter can be null. Keys in this map must * the attribute. The contents of environment are not changed by this call.
* be Strings. The appropriate type of each associated value depends on * @param name object name string to be assigned to ConnectorServer bean
* the attribute. The contents of environment are not changed by this call.
* @param name object name string to be assigned to connector server bean
* @throws Exception if unable to create connector server
*/ */
public ConnectorServer(JMXServiceURL svcUrl, Map<String,?> environment, String name) public ConnectorServer(JMXServiceURL svcUrl, Map<String, ?> environment, String name)
throws Exception
{ {
String urlPath = svcUrl.getURLPath(); this(svcUrl, environment, name, null);
int idx = urlPath.indexOf("rmi://"); }
if (idx > 0)
public ConnectorServer(JMXServiceURL svcUrl, Map<String, ?> environment, String name, SslContextFactory sslContextFactory)
{
this._jmxURL = svcUrl;
this._environment = environment == null ? new HashMap<>() : new HashMap<>(environment);
this._objectName = name;
this._sslContextFactory = sslContextFactory;
}
public JMXServiceURL getAddress()
{
return _jmxURL;
}
@Override
public void doStart() throws Exception
{
boolean rmi = "rmi".equals(_jmxURL.getProtocol());
if (rmi)
{ {
String hostPort = urlPath.substring(idx+6, urlPath.indexOf('/', idx+6)); if (!_environment.containsKey(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE))
String regHostPort = startRegistry(hostPort); _environment.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, new JMXRMIServerSocketFactory(_jmxURL.getHost(), port -> _rmiPort = port));
if (regHostPort != null) { if (_sslContextFactory != null)
urlPath = urlPath.replace(hostPort,regHostPort); {
svcUrl = new JMXServiceURL(svcUrl.getProtocol(), svcUrl.getHost(), svcUrl.getPort(), urlPath); SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory();
if (!_environment.containsKey(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE))
_environment.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf);
if (!_environment.containsKey(RMI_REGISTRY_CLIENT_SOCKET_FACTORY_ATTRIBUTE))
_environment.put(RMI_REGISTRY_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf);
} }
} }
MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
_connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(svcUrl, environment, mbeanServer);
mbeanServer.registerMBean(_connectorServer,new ObjectName(name));
}
/* ------------------------------------------------------------ */ String urlPath = _jmxURL.getURLPath();
/** String jndiRMI = "/jndi/rmi://";
* @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() if (urlPath.startsWith(jndiRMI))
*/ {
@Override int startIndex = jndiRMI.length();
public void doStart() int endIndex = urlPath.indexOf('/', startIndex);
throws Exception HostPort hostPort = new HostPort(urlPath.substring(startIndex, endIndex));
{ String registryHost = startRegistry(hostPort);
// If the RMI registry was already started, use the existing port.
if (_registryPort == 0)
_registryPort = hostPort.getPort();
urlPath = jndiRMI + registryHost + ":" + _registryPort + urlPath.substring(endIndex);
// Rebuild JMXServiceURL to use it for the creation of the JMXConnectorServer.
_jmxURL = new JMXServiceURL(_jmxURL.getProtocol(), _jmxURL.getHost(), _jmxURL.getPort(), urlPath);
}
MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
_connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(_jmxURL, _environment, mbeanServer);
mbeanServer.registerMBean(_connectorServer, new ObjectName(_objectName));
_connectorServer.start(); _connectorServer.start();
String rmiHost = normalizeHost(_jmxURL.getHost());
// If _rmiPort is still zero, it's using the same port as the RMI registry.
if (_rmiPort == 0)
_rmiPort = _registryPort;
_jmxURL = new JMXServiceURL(_jmxURL.getProtocol(), rmiHost, _rmiPort, urlPath);
ShutdownThread.register(0, this); ShutdownThread.register(0, this);
LOG.info("JMX Remote URL: {}", _connectorServer.getAddress().toString()); LOG.info("JMX URL: {}", _jmxURL);
} }
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop()
*/
@Override @Override
public void doStop() public void doStop() throws Exception
throws Exception
{ {
ShutdownThread.deregister(this); ShutdownThread.deregister(this);
_connectorServer.stop(); _connectorServer.stop();
MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
mbeanServer.unregisterMBean(new ObjectName(_objectName));
stopRegistry(); stopRegistry();
} }
/** private String startRegistry(HostPort hostPort) throws Exception
* Check that local RMI registry is used, and ensure it is started. If local RMI registry is being used and not started, start it.
*
* @param hostPath
* hostname and port number of RMI registry
* @throws Exception
*/
private String startRegistry(String hostPath) throws Exception
{ {
HostPort hostPort = new HostPort(hostPath); String host = hostPort.getHost();
int port = hostPort.getPort(1099);
String rmiHost = hostPort.getHost(); try
int rmiPort = hostPort.getPort(1099);
// Verify that local registry is being used
InetAddress hostAddress = InetAddress.getByName(rmiHost);
if(hostAddress.isLoopbackAddress())
{ {
if (rmiPort == 0) // Check if a local registry is already running.
{ LocateRegistry.getRegistry(host, port).list();
ServerSocket socket = new ServerSocket(0); return normalizeHost(host);
rmiPort = socket.getLocalPort(); }
socket.close(); catch (Throwable ex)
} {
else LOG.ignore(ex);
{
try
{
// Check if a local registry is already running
LocateRegistry.getRegistry(rmiPort).list();
return null;
}
catch (Exception ex)
{
LOG.ignore(ex);
}
}
_registry = LocateRegistry.createRegistry(rmiPort);
Thread.sleep(1000);
rmiHost = HostPort.normalizeHost(InetAddress.getLocalHost().getCanonicalHostName());
return rmiHost + ':' + Integer.toString(rmiPort);
} }
return null; RMIClientSocketFactory csf = _sslContextFactory == null ? null : new SslRMIClientSocketFactory();
RMIServerSocketFactory ssf = new JMXRMIServerSocketFactory(host, p -> _registryPort = p);
_registry = LocateRegistry.createRegistry(port, csf, ssf);
return normalizeHost(host);
}
private String normalizeHost(String host) throws UnknownHostException
{
return host == null || host.isEmpty() ? InetAddress.getLocalHost().getHostName() : host;
} }
private void stopRegistry() private void stopRegistry()
@ -180,12 +205,69 @@ public class ConnectorServer extends AbstractLifeCycle
{ {
try try
{ {
UnicastRemoteObject.unexportObject(_registry,true); UnicastRemoteObject.unexportObject(_registry, true);
} }
catch (Exception ex) catch (Exception ex)
{ {
LOG.ignore(ex); LOG.ignore(ex);
} }
finally
{
_registry = null;
}
}
}
private class JMXRMIServerSocketFactory implements RMIServerSocketFactory
{
private final String _host;
private final IntConsumer _portConsumer;
private JMXRMIServerSocketFactory(String host, IntConsumer portConsumer)
{
this._host = host;
this._portConsumer = portConsumer;
}
@Override
public ServerSocket createServerSocket(int port) throws IOException
{
InetAddress address = _host == null || _host.isEmpty() ? null : InetAddress.getByName(_host);
ServerSocket server = createServerSocket(address, port);
_portConsumer.accept(server.getLocalPort());
return server;
}
private ServerSocket createServerSocket(InetAddress address, int port) throws IOException
{
// A null address binds to the wildcard address.
if (_sslContextFactory == null)
{
ServerSocket server = new ServerSocket();
server.bind(new InetSocketAddress(address, port));
return server;
}
else
{
return _sslContextFactory.newSslServerSocket(address == null ? null : address.getHostName(), port, 0);
}
}
@Override
public int hashCode()
{
return _host != null ? _host.hashCode() : 0;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
JMXRMIServerSocketFactory that = (JMXRMIServerSocketFactory)obj;
return Objects.equals(_host, that._host);
} }
} }
} }

View File

@ -18,95 +18,217 @@
package org.eclipse.jetty.jmx; package org.eclipse.jetty.jmx;
import java.net.ConnectException;
import java.net.InetAddress; import java.net.InetAddress;
import java.rmi.registry.LocateRegistry; import java.net.ServerSocket;
import static org.junit.Assert.assertNull; import java.net.Socket;
import static org.junit.Assert.assertThat; import java.util.HashMap;
import java.util.Map;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL; import javax.management.remote.JMXServiceURL;
import javax.rmi.ssl.SslRMIClientSocketFactory;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.After; import org.junit.After;
import org.junit.Assert;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.AnyOf.anyOf;
/**
* Running the tests of this class in the same JVM results often in
* <pre>
* Caused by: java.rmi.NoSuchObjectException: no such object in table
* at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:276)
* at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:253)
* at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:379)
* at sun.rmi.registry.RegistryImpl_Stub.bind(Unknown Source)
* </pre>
* Running each test method in a forked JVM makes these tests all pass,
* therefore the issue is likely caused by use of stale stubs cached by the JDK.
*/
@Ignore
public class ConnectorServerTest public class ConnectorServerTest
{ {
private String objectName = "org.eclipse.jetty:name=rmiconnectorserver";
private ConnectorServer connectorServer; private ConnectorServer connectorServer;
@After @After
public void tearDown() throws Exception public void tearDown() throws Exception
{ {
if (connectorServer != null) if (connectorServer != null)
{ connectorServer.stop();
connectorServer.doStop();
}
} }
@Test @Test
public void randomPortTest() throws Exception public void testAddressAfterStart() throws Exception
{ {
// given connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi:///jndi/rmi:///jmxrmi"), objectName);
connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:0/jettytest"),
"org.eclipse.jetty:name=rmiconnectorserver");
// if port is not available then the server value is null
if (connectorServer != null)
{
// when
connectorServer.start();
// then
assertThat("Server status must be in started or starting",connectorServer.getState(),
anyOf(is(ConnectorServer.STARTED),is(ConnectorServer.STARTING)));
}
}
@Test
@Ignore // collides on ci server
public void testConnServerWithRmiDefaultPort() throws Exception
{
// given
LocateRegistry.createRegistry(1099);
JMXServiceURL serviceURLWithOutPort = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi");
connectorServer = new ConnectorServer(serviceURLWithOutPort," domain: key3 = value3");
// when
connectorServer.start(); connectorServer.start();
// then JMXServiceURL address = connectorServer.getAddress();
assertThat("Server status must be in started or starting",connectorServer.getState(),anyOf(is(ConnectorServer.STARTED),is(ConnectorServer.STARTING))); Assert.assertTrue(address.toString().matches("service:jmx:rmi://[^:]+:\\d+/jndi/rmi://[^:]+:\\d+/jmxrmi"));
} }
@Test @Test
public void testConnServerWithRmiRandomPort() throws Exception public void testNoRegistryHostBindsToAny() throws Exception
{ {
// given connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi:///jndi/rmi:///jmxrmi"), objectName);
JMXServiceURL serviceURLWithOutPort = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:1199/jmxrmi"); connectorServer.start();
connectorServer = new ConnectorServer(serviceURLWithOutPort," domain: key4 = value4");
// if port is not available then the server value is null
if (connectorServer != null)
{
// when
connectorServer.start();
connectorServer.stop();
// then // Verify that I can connect to the RMI registry using a non-loopback address.
assertThat("Server status must be in started or starting",connectorServer.getState(), new Socket(InetAddress.getLocalHost(), 1099).close();
anyOf(is(ConnectorServer.STOPPING),is(ConnectorServer.STOPPED))); // Verify that I can connect to the RMI registry using the loopback address.
new Socket(InetAddress.getLoopbackAddress(), 1099).close();
}
@Test
public void testNoRegistryHostNonDefaultRegistryPort() throws Exception
{
int registryPort = 1299;
connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi:///jndi/rmi://:" + registryPort + "/jmxrmi"), objectName);
connectorServer.start();
// Verify that I can connect to the RMI registry using a non-loopback address.
new Socket(InetAddress.getLocalHost(), registryPort).close();
// Verify that I can connect to the RMI registry using the loopback address.
new Socket(InetAddress.getLoopbackAddress(), registryPort).close();
}
@Test
public void testNoRMIHostBindsToAny() throws Exception
{
connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi:///jndi/rmi:///jmxrmi"), objectName);
connectorServer.start();
// Verify that I can connect to the RMI server using a non-loopback address.
new Socket(InetAddress.getLocalHost(), connectorServer.getAddress().getPort()).close();
// Verify that I can connect to the RMI server using the loopback address.
new Socket(InetAddress.getLoopbackAddress(), connectorServer.getAddress().getPort()).close();
}
@Test
public void testLocalhostRegistryBindsToLoopback() throws Exception
{
connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi"), objectName);
connectorServer.start();
InetAddress localHost = InetAddress.getLocalHost();
if (!localHost.isLoopbackAddress())
{
try
{
// Verify that I cannot connect to the RMIRegistry using a non-loopback address.
new Socket(localHost, 1099);
Assert.fail();
}
catch (ConnectException ignored)
{
// Ignored.
}
}
InetAddress loopback = InetAddress.getLoopbackAddress();
new Socket(loopback, 1099).close();
}
@Test
public void testLocalhostRMIBindsToLoopback() throws Exception
{
connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi"), objectName);
connectorServer.start();
JMXServiceURL address = connectorServer.getAddress();
InetAddress localHost = InetAddress.getLocalHost();
if (!localHost.isLoopbackAddress())
{
try
{
// Verify that I cannot connect to the RMIRegistry using a non-loopback address.
new Socket(localHost, address.getPort());
Assert.fail();
}
catch (ConnectException ignored)
{
// Ignored.
}
}
InetAddress loopback = InetAddress.getLoopbackAddress();
new Socket(loopback, address.getPort()).close();
}
@Test
public void testRMIServerPort() throws Exception
{
ServerSocket server = new ServerSocket(0);
int port = server.getLocalPort();
server.close();
connectorServer = new ConnectorServer(new JMXServiceURL("service:jmx:rmi://localhost:" + port + "/jndi/rmi:///jmxrmi"), objectName);
connectorServer.start();
JMXServiceURL address = connectorServer.getAddress();
Assert.assertEquals(port, address.getPort());
InetAddress loopback = InetAddress.getLoopbackAddress();
new Socket(loopback, port).close();
}
@Test
public void testRMIServerAndRMIRegistryOnSameHostAndSamePort() throws Exception
{
// RMI can multiplex connections on the same address and port for different
// RMI objects, in this case the RMI registry and the RMI server. In this
// case, the RMIServerSocketFactory will be invoked only once.
// The case with different address and same port is already covered by TCP,
// that can listen to 192.168.0.1:1099 and 127.0.0.1:1099 without problems.
String host = "localhost";
int port = 1399;
connectorServer = new ConnectorServer(new JMXServiceURL("rmi", host, port, "/jndi/rmi://" + host + ":" + port + "/jmxrmi"), objectName);
connectorServer.start();
JMXServiceURL address = connectorServer.getAddress();
Assert.assertEquals(port, address.getPort());
}
@Test
public void testJMXOverTLS() throws Exception
{
SslContextFactory sslContextFactory = new SslContextFactory();
String keyStorePath = MavenTestingUtils.getTestResourcePath("keystore.jks").toString();
String keyStorePassword = "storepwd";
sslContextFactory.setKeyStorePath(keyStorePath);
sslContextFactory.setKeyStorePassword(keyStorePassword);
sslContextFactory.start();
// The RMIClientSocketFactory is stored within the RMI stub.
// When using TLS, the stub is deserialized in a possibly different
// JVM that does not have access to the server keystore, and there
// is no way to provide TLS configuration during the deserialization
// of the stub. Therefore the client must provide system properties
// to specify the TLS configuration. For this test it needs the
// trustStore because the server certificate is self-signed.
// The server needs to contact the RMI registry and therefore also
// needs these system properties.
System.setProperty("javax.net.ssl.trustStore", keyStorePath);
System.setProperty("javax.net.ssl.trustStorePassword", keyStorePassword);
connectorServer = new ConnectorServer(new JMXServiceURL("rmi", null, 1100, "/jndi/rmi://localhost:1100/jmxrmi"), null, objectName, sslContextFactory);
connectorServer.start();
// The client needs to talk TLS to the RMI registry to download
// the RMI server stub, and this is independent from JMX.
// The RMI server stub then contains the SslRMIClientSocketFactory
// needed to talk to the RMI server.
Map<String, Object> clientEnv = new HashMap<>();
clientEnv.put(ConnectorServer.RMI_REGISTRY_CLIENT_SOCKET_FACTORY_ATTRIBUTE, new SslRMIClientSocketFactory());
try (JMXConnector client = JMXConnectorFactory.connect(connectorServer.getAddress(), clientEnv))
{
client.getMBeanServerConnection().queryNames(null, null);
} }
} }
@Test
@Ignore
public void testIsLoopbackAddressWithWrongValue() throws Exception
{
// given
JMXServiceURL serviceURLWithOutPort = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + InetAddress.getLocalHost() + ":1199/jmxrmi");
// when
connectorServer = new ConnectorServer(serviceURLWithOutPort," domain: key5 = value5");
// then
assertNull("As loopback address returns false...registry must be null",connectorServer._registry);
}
} }

View File

@ -1,2 +1,2 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
org.eclipse.jetty.jmx.LEVEL=WARN org.eclipse.jetty.jmx.LEVEL=INFO

Binary file not shown.

View File

@ -22,14 +22,11 @@ package org.eclipse.jetty.quickstart;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.io.File; import java.io.File;
import java.nio.file.Path;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -90,7 +87,7 @@ public class TestQuickStart
server.start(); server.start();
//verify that FooServlet is now mapped to / and not the DefaultServlet //verify that FooServlet is now mapped to / and not the DefaultServlet
ServletHolder sh = webapp.getServletHandler().getHolderEntry("/").getResource(); ServletHolder sh = webapp.getServletHandler().getMappedServlet("/").getResource();
assertNotNull(sh); assertNotNull(sh);
assertEquals("foo", sh.getName()); assertEquals("foo", sh.getName());
server.stop(); server.stop();

View File

@ -50,8 +50,7 @@ public class HashLoginService extends AbstractLoginService
{ {
private static final Logger LOG = Log.getLogger(HashLoginService.class); private static final Logger LOG = Log.getLogger(HashLoginService.class);
private File _configFile; private String _config;
private Resource _configResource;
private boolean hotReload = false; // default is not to reload private boolean hotReload = false; // default is not to reload
private UserStore _userStore; private UserStore _userStore;
private boolean _userStoreAutoCreate = false; private boolean _userStoreAutoCreate = false;
@ -78,28 +77,15 @@ public class HashLoginService extends AbstractLoginService
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public String getConfig() public String getConfig()
{ {
if(_configFile == null) return _config;
{
return null;
}
return _configFile.getAbsolutePath();
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/**
* @deprecated use {@link #setConfig(String)} instead
*/
@Deprecated @Deprecated
public void getConfig(String config)
{
setConfig(config);
}
/* ------------------------------------------------------------ */
public Resource getConfigResource() public Resource getConfigResource()
{ {
return _configResource; return null;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -109,12 +95,11 @@ public class HashLoginService extends AbstractLoginService
* The property file maps usernames to password specs followed by an optional comma separated list of role names. * The property file maps usernames to password specs followed by an optional comma separated list of role names.
* </p> * </p>
* *
* @param configFile * @param config uri or url or path to realm properties file
* Filename of user properties file.
*/ */
public void setConfig(String configFile) public void setConfig(String config)
{ {
_configFile = new File(configFile); _config=config;
} }
/** /**
@ -200,11 +185,10 @@ public class HashLoginService extends AbstractLoginService
if (_userStore == null) if (_userStore == null)
{ {
if(LOG.isDebugEnabled()) if(LOG.isDebugEnabled())
LOG.debug("doStart: Starting new PropertyUserStore. PropertiesFile: " + _configFile + " hotReload: " + hotReload); LOG.debug("doStart: Starting new PropertyUserStore. PropertiesFile: " + _config + " hotReload: " + hotReload);
PropertyUserStore propertyUserStore = new PropertyUserStore(); PropertyUserStore propertyUserStore = new PropertyUserStore();
propertyUserStore.setHotReload(hotReload); propertyUserStore.setHotReload(hotReload);
propertyUserStore.setConfigPath(_configFile); propertyUserStore.setConfigPath(_config);
propertyUserStore.start(); propertyUserStore.start();
_userStore = propertyUserStore; _userStore = propertyUserStore;
_userStoreAutoCreate = true; _userStoreAutoCreate = true;

View File

@ -29,6 +29,7 @@ import org.eclipse.jetty.util.security.Credential;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
@ -73,18 +74,30 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener
@Deprecated @Deprecated
public String getConfig() public String getConfig()
{ {
return _configPath.toString(); if (_configPath != null)
return _configPath.toString();
return null;
} }
/** /**
* Set the Config Path from a String reference to a file * Set the Config Path from a String reference to a file
* @param configFile the config file * @param config the config file
* @deprecated use {@link #setConfigPath(String)} instead
*/ */
@Deprecated public void setConfig(String config)
public void setConfig(String configFile)
{ {
setConfigPath(configFile); try
{
Resource configResource = Resource.newResource(config);
if (configResource.getFile() != null)
setConfigPath(configResource.getFile());
else
throw new IllegalArgumentException(config+" is not a file");
}
catch (Exception e)
{
throw new IllegalStateException(e);
}
} }
/** /**

View File

@ -513,7 +513,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory, Welc
if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null) if ((_welcomeServlets || _welcomeExactServlets) && welcome_servlet==null)
{ {
MappedResource<ServletHolder> entry=_servletHandler.getHolderEntry(welcome_in_context); MappedResource<ServletHolder> entry=_servletHandler.getMappedServlet(welcome_in_context);
if (entry!=null && entry.getResource()!=_defaultHolder && if (entry!=null && entry.getResource()!=_defaultHolder &&
(_welcomeServlets || (_welcomeExactServlets && entry.getPathSpec().getDeclaration().equals(welcome_in_context)))) (_welcomeServlets || (_welcomeExactServlets && entry.getPathSpec().getDeclaration().equals(welcome_in_context))))
welcome_servlet=welcome_in_context; welcome_servlet=welcome_in_context;

View File

@ -32,7 +32,6 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.PathMap.MappedEntry;
import org.eclipse.jetty.http.pathmap.MappedResource; import org.eclipse.jetty.http.pathmap.MappedResource;
import org.eclipse.jetty.server.Dispatcher; import org.eclipse.jetty.server.Dispatcher;
import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Handler;
@ -66,6 +65,7 @@ import org.eclipse.jetty.util.log.Logger;
* @version $Id: Invoker.java 4780 2009-03-17 15:36:08Z jesse $ * @version $Id: Invoker.java 4780 2009-03-17 15:36:08Z jesse $
* *
*/ */
@SuppressWarnings("serial")
public class Invoker extends HttpServlet public class Invoker extends HttpServlet
{ {
private static final Logger LOG = Log.getLogger(Invoker.class); private static final Logger LOG = Log.getLogger(Invoker.class);
@ -168,11 +168,11 @@ public class Invoker extends HttpServlet
synchronized(_servletHandler) synchronized(_servletHandler)
{ {
// find the entry for the invoker (me) // find the entry for the invoker (me)
_invokerEntry=_servletHandler.getHolderEntry(servlet_path); _invokerEntry=_servletHandler.getMappedServlet(servlet_path);
// Check for existing mapping (avoid threaded race). // Check for existing mapping (avoid threaded race).
String path=URIUtil.addPaths(servlet_path,servlet); String path=URIUtil.addPaths(servlet_path,servlet);
MappedResource<ServletHolder> entry = _servletHandler.getHolderEntry(path); MappedResource<ServletHolder> entry = _servletHandler.getMappedServlet(path);
if (entry!=null && !entry.equals(_invokerEntry)) if (entry!=null && !entry.equals(_invokerEntry))
{ {

View File

@ -24,7 +24,6 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import java.util.Map; import java.util.Map;
@ -53,8 +52,6 @@ import org.eclipse.jetty.http.pathmap.MappedResource;
import org.eclipse.jetty.http.pathmap.PathMappings; import org.eclipse.jetty.http.pathmap.PathMappings;
import org.eclipse.jetty.http.pathmap.PathSpec; import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.http.pathmap.ServletPathSpec; import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.security.IdentityService; import org.eclipse.jetty.security.IdentityService;
import org.eclipse.jetty.security.SecurityHandler; import org.eclipse.jetty.security.SecurityHandler;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
@ -361,14 +358,16 @@ public class ServletHandler extends ScopedHandler
/** /**
* ServletHolder matching path. * ServletHolder matching path.
* *
* @param pathInContext Path within _context. * @param target Path within _context or servlet name
* @return PathMap Entries pathspec to ServletHolder * @return PathMap Entries pathspec to ServletHolder
* @deprecated Use {@link #getMappedServlet(String)}
*/ */
public MappedResource<ServletHolder> getHolderEntry(String pathInContext) @Deprecated
public MappedResource<ServletHolder> getHolderEntry(String target)
{ {
if (_servletPathMap==null) if (target.startsWith("/"))
return null; return getMappedServlet(target);
return _servletPathMap.getMatch(pathInContext); return null;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -438,16 +437,14 @@ public class ServletHandler extends ScopedHandler
ServletHolder servlet_holder=null; ServletHolder servlet_holder=null;
UserIdentity.Scope old_scope=null; UserIdentity.Scope old_scope=null;
// find the servlet MappedResource<ServletHolder> mapping=getMappedServlet(target);
if (target.startsWith("/")) if (mapping!=null)
{ {
// Look for the servlet by path servlet_holder = mapping.getResource();
MappedResource<ServletHolder> entry=getHolderEntry(target);
if (entry!=null) if (mapping.getPathSpec()!=null)
{ {
PathSpec pathSpec = entry.getPathSpec(); PathSpec pathSpec = mapping.getPathSpec();
servlet_holder=entry.getResource();
String servlet_path=pathSpec.getPathMatch(target); String servlet_path=pathSpec.getPathMatch(target);
String path_info=pathSpec.getPathInfo(target); String path_info=pathSpec.getPathInfo(target);
@ -464,12 +461,7 @@ public class ServletHandler extends ScopedHandler
} }
} }
} }
else
{
// look for a servlet by name!
servlet_holder= _servletNameMap.get(target);
}
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("servlet {}|{}|{} -> {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),servlet_holder); LOG.debug("servlet {}|{}|{} -> {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),servlet_holder);
@ -550,7 +542,33 @@ public class ServletHandler extends ScopedHandler
baseRequest.setHandled(true); baseRequest.setHandled(true);
} }
} }
/* ------------------------------------------------------------ */
/**
* ServletHolder matching path.
*
* @param target Path within _context or servlet name
* @return MappedResource to the ServletHolder. Named servlets have a null PathSpec
*/
public MappedResource<ServletHolder> getMappedServlet(String target)
{
if (target.startsWith("/"))
{
if (_servletPathMap==null)
return null;
return _servletPathMap.getMatch(target);
}
if (_servletNameMap==null)
return null;
ServletHolder holder = _servletNameMap.get(target);
if (holder==null)
return null;
return new MappedResource<>(null,holder);
}
/* ------------------------------------------------------------ */
protected FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder) protected FilterChain getFilterChain(Request baseRequest, String pathInContext, ServletHolder servletHolder)
{ {
String key=pathInContext==null?servletHolder.getName():pathInContext; String key=pathInContext==null?servletHolder.getName():pathInContext;
@ -704,8 +722,6 @@ public class ServletHandler extends ScopedHandler
return _startWithUnavailable; return _startWithUnavailable;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** Initialize filters and load-on-startup servlets. /** Initialize filters and load-on-startup servlets.
* @throws Exception if unable to initialize * @throws Exception if unable to initialize
@ -1766,6 +1782,7 @@ public class ServletHandler extends ScopedHandler
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@SuppressWarnings("serial")
public static class Default404Servlet extends HttpServlet public static class Default404Servlet extends HttpServlet
{ {
@Override @Override

View File

@ -267,7 +267,7 @@ public class ServletHandlerTest
handler.updateMappings(); handler.updateMappings();
MappedResource<ServletHolder> entry=handler.getHolderEntry("/foo/*"); MappedResource<ServletHolder> entry=handler.getMappedServlet("/foo/*");
assertNotNull(entry); assertNotNull(entry);
assertEquals("s1", entry.getResource().getName()); assertEquals("s1", entry.getResource().getName());
} }
@ -312,7 +312,7 @@ public class ServletHandlerTest
handler.addServletMapping(sm2); handler.addServletMapping(sm2);
handler.updateMappings(); handler.updateMappings();
MappedResource<ServletHolder> entry=handler.getHolderEntry("/foo/*"); MappedResource<ServletHolder> entry=handler.getMappedServlet("/foo/*");
assertNotNull(entry); assertNotNull(entry);
assertEquals("s2", entry.getResource().getName()); assertEquals("s2", entry.getResource().getName());
} }

View File

@ -41,6 +41,13 @@ public class JettyWebXmlConfiguration extends AbstractConfiguration
{ {
private static final Logger LOG = Log.getLogger(JettyWebXmlConfiguration.class); private static final Logger LOG = Log.getLogger(JettyWebXmlConfiguration.class);
/** The value of this property points to the WEB-INF directory of
* the web-app currently installed.
* it is passed as a property to the jetty-web.xml file */
@Deprecated
public static final String PROPERTY_THIS_WEB_INF_URL = "this.web-inf.url";
public static final String PROPERTY_WEB_INF_URI = "web-inf.uri";
public static final String PROPERTY_WEB_INF = "web-inf";
public static final String XML_CONFIGURATION = "org.eclipse.jetty.webapp.JettyWebXmlConfiguration"; public static final String XML_CONFIGURATION = "org.eclipse.jetty.webapp.JettyWebXmlConfiguration";
public static final String JETTY_WEB_XML = "jetty-web.xml"; public static final String JETTY_WEB_XML = "jetty-web.xml";
@ -104,12 +111,8 @@ public class JettyWebXmlConfiguration extends AbstractConfiguration
private void setupXmlConfiguration(XmlConfiguration jetty_config, Resource web_inf) throws IOException private void setupXmlConfiguration(XmlConfiguration jetty_config, Resource web_inf) throws IOException
{ {
Map<String,String> props = jetty_config.getProperties(); Map<String,String> props = jetty_config.getProperties();
props.put("this.web-inf.url", web_inf.getURI().toURL().toExternalForm()); props.put(PROPERTY_THIS_WEB_INF_URL, web_inf.getURI().toString());
String webInfPath = web_inf.getFile().getAbsolutePath(); props.put(PROPERTY_WEB_INF_URI, web_inf.getURI().toString());
if (!webInfPath.endsWith(File.separator)) props.put(PROPERTY_WEB_INF, web_inf.toString());
{
webInfPath += File.separator;
}
props.put("this.web-inf.path", webInfPath);
} }
} }

View File

@ -51,7 +51,9 @@ detected.
<Set name="loginService"> <Set name="loginService">
<New class="org.eclipse.jetty.security.HashLoginService"> <New class="org.eclipse.jetty.security.HashLoginService">
<Set name="name">Test Realm</Set> <Set name="name">Test Realm</Set>
<Set name="config"><Property name="this.web-inf.path"/>realm.properties</Set> <Set name="config">
<Property name="web-inf.uri">/realm.properties</Property>
</Set>
<!-- To enable reload of realm when properties change, uncomment the following lines --> <!-- To enable reload of realm when properties change, uncomment the following lines -->
<!-- <!--
<Set name="hotReload">false</Set> <Set name="hotReload">false</Set>

View File

@ -20,6 +20,8 @@
</excludes> </excludes>
</fileSet> </fileSet>
</fileSets> </fileSets>
<!-- Removed until PropertyUserStore supports packed realm.properties -->
<!--
<files> <files>
<file> <file>
<source>src/main/assembly/embedded-jetty-web-for-webbundle.xml</source> <source>src/main/assembly/embedded-jetty-web-for-webbundle.xml</source>
@ -32,4 +34,5 @@
<destName>realm.properties</destName> <destName>realm.properties</destName>
</file> </file>
</files> </files>
-->
</assembly> </assembly>