This is an automated email from the ASF dual-hosted git repository. lgoldstein pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
The following commit(s) were added to refs/heads/master by this push: new 0072493 [SSHD-931] Using an executor supplier instead of a specific instance in SftpSubsystemFactory 0072493 is described below commit 0072493b2cd5a5972a73bad15902eb3698e443f4 Author: Lyor Goldstein <lgoldst...@apache.org> AuthorDate: Mon Aug 5 09:41:51 2019 +0300 [SSHD-931] Using an executor supplier instead of a specific instance in SftpSubsystemFactory --- CHANGES.md | 14 +++++ docs/sftp.md | 70 ++++++++++++++-------- .../subsystem/sftp/SftpSubsystemFactory.java | 31 ++++++---- .../client/subsystem/sftp/SftpVersionsTest.java | 4 +- .../helpers/SpaceAvailableExtensionImplTest.java | 2 +- .../openssh/helpers/OpenSSHExtensionsTest.java | 2 +- .../subsystem/sftp/SftpSubsystemFactoryTest.java | 17 +++--- 7 files changed, 92 insertions(+), 48 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5a416ae..f1b25ee 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,18 @@ ## Major code re-factoring +* `SftpSubSystemFactory` and its `Builder` use a `Supplier<CloseableExecutorService>` instead of +an executor instance in order to allow users to provide a "fresh" instance every time an SFTP +session is initiated and protect their instance from shutdown when session is destroyed: + +```java + CloseableExecutorService mySpecialExecutor = ...; + SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder() + .withExecutorServiceProvider(() -> ThreadUtils.noClose(mySpecialExecutor)) + .build(); + server.setSubsystemFactories(Collections.singletonList(factory)); +``` + ## Minor code helpers * `SessionListener` supports `sessionPeerIdentificationReceived` that is invoked once successful @@ -16,5 +28,7 @@ peer version data is received. * [SSHD-930](https://issues.apache.org/jira/browse/SSHD-930) - Added configuration allowing the user to specify whether client should wait for the server's identification before sending its own. +* [SSHD-931](https://issues.apache.org/jira/browse/SSHD-931) - Using an executor supplier instead of a specific instance in `SftpSubsystemFactory` + * [SSHD-934](https://issues.apache.org/jira/browse/SSHD-934) - Fixed ECDSA public key encoding into OpenSSH format. diff --git a/docs/sftp.md b/docs/sftp.md index 49dee63..919d761 100644 --- a/docs/sftp.md +++ b/docs/sftp.md @@ -1,4 +1,4 @@ -## SFTP +# SFTP Both client-side and server-side SFTP are supported. Starting from version 2.0, the SFTP related code is located in the `sshd-sftp` artifact, so one needs to add this additional dependency to one's maven project: @@ -13,18 +13,35 @@ in the `sshd-sftp` artifact, so one needs to add this additional dependency to o ``` -### Server-side SFTP +## Server-side SFTP On the server side, the following code needs to be added: ```java SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder() + ...with... + ...with... .build(); server.setSubsystemFactories(Collections.singletonList(factory)); ``` +**Note:** the factory uses an ad-hoc `CloseableExecutorService` in order to spawn the necessary threads +for processing the protocol messages. The user can provide a custom `Supplier` of such a service - however, +it must be protected from shutdown if the user needs it to remain active between successive SFTP session. +This can be done via the `ThreadUtils#noClose` utility: + +```java + + CloseableExecutorService mySpecialExecutor = ...; + SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder() + .withExecutorServiceProvider(() -> ThreadUtils.noClose(mySpecialExecutor)) + .build(); + server.setSubsystemFactories(Collections.singletonList(factory)); + +``` + ### `SftpEventListener` Provides information about major SFTP protocol events. The provided `File/DirectoryHandle` to the various callbacks can also be used to @@ -87,7 +104,29 @@ forces a synchronization of the data with the file-system. This behavior can be by setting the `sftp-auto-fsync-on-close` property to *false* (or by providing a customized implementation that involves other considerations as well). -### Client-side SFTP +### Internal exceptions and error message handling + +If an exception is thrown during processing of an SFTP command, then the exception is translated into a `SSH_FXP_STATUS` message +using a registered `SftpErrorStatusDataHandler`. The default implementation provides a short description of the failure based on the thrown +exception type. However, users may override it when creating the `SftpSubsystemFactory` and provide their own codes and/or messages - e.g., +for debugging one can register a `DetailedSftpErrorStatusDataHandler` (see `sshd-contrib`) that "leaks" more information in the generated message. + +If the registered handler implements `ChannelSessionAware` then it will also be informed of the registered `ChannelSession` when it is +provided to the `SftpSubsystem` itself. This can be used to register an extended data writer that can handle data sent via the STDERR +channel. **Note:** this feature is allowed according to [SFTP version 4 - section 3.1](https://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-3.1): + +>> Packets are sent and received on stdout and stdin. Data sent on stderr by the server SHOULD be considered debug +>> or supplemental error information, and MAY be displayed to the user. + +however, the current code provides no built-in support for this feature. + +If registering an extended data writer then one should take care of any race conditions that may occur where (extended) data +may arrive before the handler is informed of the existence of the `ChannelSession`. For this purpose one should configure a +reasonable buffer size by setting the `channel-session-max-extdata-bufsize` property. This way, if any data arrives before the +extended data handler is registered it will be buffered (up to the specified max. size). **Note:** if a buffer size is configured +but no extended data handler is registered when channel is spawning the command then an exception will occur. + +## Client-side SFTP In order to obtain an `SftpClient` instance one needs to use an `SftpClientFactory`: @@ -277,7 +316,7 @@ configuration keys and values. ``` -#### Configuring the client session used to create an `SftpFileSystem` +### Configuring the client session used to create an `SftpFileSystem` It is possible to register a `SftpFileSystemClientSessionInitializer` with the provider instead of the default one and thus better control the `ClientSession` used to generate the file-system instance. The default implementation @@ -363,6 +402,8 @@ UTF-8 is used. **Note:** the value can be a charset name or a `java.nio.charset. ``` +## Extensions + Both client and server support several of the SFTP extensions specified in various drafts: * `supported` - [DRAFT 05 - section 4.4](http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-05.tx) @@ -458,24 +499,3 @@ for sending and receiving the newly added extension. See how other extensions are implemented and follow their example -### Internal exceptions and error message handling - -If an exception is thrown during processing of an SFTP command, then the exception is translated into a `SSH_FXP_STATUS` message -using a registered `SftpErrorStatusDataHandler`. The default implementation provides a short description of the failure based on the thrown -exception type. However, users may override it when creating the `SftpSubsystemFactory` and provide their own codes and/or messages - e.g., -for debugging one can register a `DetailedSftpErrorStatusDataHandler` (see `sshd-contrib`) that "leaks" more information in the generated message. - -If the registered handler implements `ChannelSessionAware` then it will also be informed of the registered `ChannelSession` when it is -provided to the `SftpSubsystem` itself. This can be used to register an extended data writer that can handle data sent via the STDERR -channel. **Note:** this feature is allowed according to [SFTP version 4 - section 3.1](https://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-3.1): - ->> Packets are sent and received on stdout and stdin. Data sent on stderr by the server SHOULD be considered debug ->> or supplemental error information, and MAY be displayed to the user. - -however, the current code provides no built-in support for this feature. - -If registering an extended data writer then one should take care of any race conditions that may occur where (extended) data -may arrive before the handler is informed of the existence of the `ChannelSession`. For this purpose one should configure a -reasonable buffer size by setting the `channel-session-max-extdata-bufsize` property. This way, if any data arrives before the -extended data handler is registered it will be buffered (up to the specified max. size). **Note:** if a buffer size is configured -but no extended data handler is registered when channel is spawning the command then an exception will occur. diff --git a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java index 056b5fa..0dfae8d 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactory.java @@ -20,6 +20,7 @@ package org.apache.sshd.server.subsystem.sftp; import java.util.Objects; +import java.util.function.Supplier; import org.apache.sshd.common.subsystem.sftp.SftpConstants; import org.apache.sshd.common.util.GenericUtils; @@ -39,7 +40,7 @@ public class SftpSubsystemFactory public static final UnsupportedAttributePolicy DEFAULT_POLICY = UnsupportedAttributePolicy.Warn; public static class Builder extends AbstractSftpEventListenerManager implements ObjectBuilder<SftpSubsystemFactory> { - private CloseableExecutorService executors; + private Supplier<? extends CloseableExecutorService> executorsProvider; private UnsupportedAttributePolicy policy = DEFAULT_POLICY; private SftpFileSystemAccessor fileSystemAccessor = SftpFileSystemAccessor.DEFAULT; private SftpErrorStatusDataHandler errorStatusDataHandler = SftpErrorStatusDataHandler.DEFAULT; @@ -48,8 +49,8 @@ public class SftpSubsystemFactory super(); } - public Builder withExecutorService(CloseableExecutorService service) { - executors = service; + public Builder withExecutorServiceProvider(Supplier<? extends CloseableExecutorService> provider) { + executorsProvider = provider; return this; } @@ -71,7 +72,7 @@ public class SftpSubsystemFactory @Override public SftpSubsystemFactory build() { SftpSubsystemFactory factory = new SftpSubsystemFactory(); - factory.setExecutorService(executors); + factory.setExecutorServiceProvider(executorsProvider); factory.setUnsupportedAttributePolicy(policy); factory.setFileSystemAccessor(fileSystemAccessor); factory.setErrorStatusDataHandler(errorStatusDataHandler); @@ -80,7 +81,7 @@ public class SftpSubsystemFactory } } - private CloseableExecutorService executors; + private Supplier<? extends CloseableExecutorService> executorsProvider; private UnsupportedAttributePolicy policy = DEFAULT_POLICY; private SftpFileSystemAccessor fileSystemAccessor = SftpFileSystemAccessor.DEFAULT; private SftpErrorStatusDataHandler errorStatusDataHandler = SftpErrorStatusDataHandler.DEFAULT; @@ -94,16 +95,17 @@ public class SftpSubsystemFactory return NAME; } - public CloseableExecutorService getExecutorService() { - return executors; + public Supplier<? extends CloseableExecutorService> getExecutorServiceProvider() { + return executorsProvider; } /** - * @param service The {@link CloseableExecutorService} to be used by the {@link SftpSubsystem} - * command when starting execution. If {@code null} then a single-threaded ad-hoc service is used. + * @param service The {@link Supplier} of {@link CloseableExecutorService}-s to be used by the + * {@link SftpSubsystem} command when starting execution. If {@code null} then a single-threaded + * ad-hoc service is used. */ - public void setExecutorService(CloseableExecutorService service) { - executors = service; + public void setExecutorServiceProvider(Supplier<? extends CloseableExecutorService> provider) { + executorsProvider = provider; } public UnsupportedAttributePolicy getUnsupportedAttributePolicy() { @@ -136,10 +138,15 @@ public class SftpSubsystemFactory errorStatusDataHandler = Objects.requireNonNull(handler, "No error status data handler provided"); } + protected CloseableExecutorService resolveExecutorService() { + Supplier<? extends CloseableExecutorService> provider = getExecutorServiceProvider(); + return (provider == null) ? null : provider.get(); + } + @Override public Command create() { SftpSubsystem subsystem = - new SftpSubsystem(getExecutorService(), + new SftpSubsystem(resolveExecutorService(), getUnsupportedAttributePolicy(), getFileSystemAccessor(), getErrorStatusDataHandler()); GenericUtils.forEach(getRegisteredListeners(), subsystem::addSftpEventListener); diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java index 80d149f..af3514a 100644 --- a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java +++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpVersionsTest.java @@ -245,7 +245,7 @@ public class SftpVersionsTest extends AbstractSftpClientTestSupport { SftpSubsystemFactory factory = new SftpSubsystemFactory() { @Override public Command create() { - SftpSubsystem subsystem = new SftpSubsystem(getExecutorService(), + SftpSubsystem subsystem = new SftpSubsystem(resolveExecutorService(), getUnsupportedAttributePolicy(), getFileSystemAccessor(), getErrorStatusDataHandler()) { @Override protected NavigableMap<String, Object> resolveFileAttributes(Path file, int flags, LinkOption... options) @@ -369,7 +369,7 @@ public class SftpVersionsTest extends AbstractSftpClientTestSupport { SftpSubsystemFactory factory = new SftpSubsystemFactory() { @Override public Command create() { - SftpSubsystem subsystem = new SftpSubsystem(getExecutorService(), + SftpSubsystem subsystem = new SftpSubsystem(resolveExecutorService(), getUnsupportedAttributePolicy(), getFileSystemAccessor(), getErrorStatusDataHandler()) { @Override protected NavigableMap<String, Object> resolveFileAttributes(Path file, int flags, LinkOption... options) diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/SpaceAvailableExtensionImplTest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/SpaceAvailableExtensionImplTest.java index a528278..a2a73e1 100644 --- a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/SpaceAvailableExtensionImplTest.java +++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/helpers/SpaceAvailableExtensionImplTest.java @@ -71,7 +71,7 @@ public class SpaceAvailableExtensionImplTest extends AbstractSftpClientTestSuppo sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory() { @Override public Command create() { - return new SftpSubsystem(getExecutorService(), + return new SftpSubsystem(resolveExecutorService(), getUnsupportedAttributePolicy(), getFileSystemAccessor(), getErrorStatusDataHandler()) { @Override protected SpaceAvailableExtensionInfo doSpaceAvailable(int id, String path) throws IOException { diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/helpers/OpenSSHExtensionsTest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/helpers/OpenSSHExtensionsTest.java index 5e31a23..4cc7a46 100644 --- a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/helpers/OpenSSHExtensionsTest.java +++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/extensions/openssh/helpers/OpenSSHExtensionsTest.java @@ -126,7 +126,7 @@ public class OpenSSHExtensionsTest extends AbstractSftpClientTestSupport { sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory() { @Override public Command create() { - return new SftpSubsystem(getExecutorService(), + return new SftpSubsystem(resolveExecutorService(), getUnsupportedAttributePolicy(), getFileSystemAccessor(), getErrorStatusDataHandler()) { @Override protected List<OpenSSHExtension> resolveOpenSSHExtensions(ServerSession session) { diff --git a/sshd-sftp/src/test/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactoryTest.java b/sshd-sftp/src/test/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactoryTest.java index da722f2..45799b2 100644 --- a/sshd-sftp/src/test/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactoryTest.java +++ b/sshd-sftp/src/test/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystemFactoryTest.java @@ -45,7 +45,7 @@ public class SftpSubsystemFactoryTest extends JUnitTestSupport { @Test public void testBuilderDefaultFactoryValues() { SftpSubsystemFactory factory = new SftpSubsystemFactory.Builder().build(); - assertNull("Mismatched executor", factory.getExecutorService()); + assertNull("Mismatched executor", factory.resolveExecutorService()); assertSame("Mismatched unsupported attribute policy", SftpSubsystemFactory.DEFAULT_POLICY, factory.getUnsupportedAttributePolicy()); } @@ -56,9 +56,10 @@ public class SftpSubsystemFactoryTest extends JUnitTestSupport { public void testBuilderCorrectlyInitializesFactory() { SftpSubsystemFactory.Builder builder = new SftpSubsystemFactory.Builder(); CloseableExecutorService service = dummyExecutor(); - SftpSubsystemFactory factory = builder.withExecutorService(service) + SftpSubsystemFactory factory = + builder.withExecutorServiceProvider(() -> service) .build(); - assertSame("Mismatched executor", service, factory.getExecutorService()); + assertSame("Mismatched executor", service, factory.resolveExecutorService()); for (UnsupportedAttributePolicy policy : UnsupportedAttributePolicy.VALUES) { SftpSubsystemFactory actual = builder.withUnsupportedAttributePolicy(policy).build(); @@ -82,13 +83,15 @@ public class SftpSubsystemFactoryTest extends JUnitTestSupport { @Test public void testBuilderUniqueInstance() { SftpSubsystemFactory.Builder builder = new SftpSubsystemFactory.Builder(); - SftpSubsystemFactory f1 = builder.withExecutorService(dummyExecutor()).build(); + CloseableExecutorService service1 = dummyExecutor(); + SftpSubsystemFactory f1 = builder.withExecutorServiceProvider(() -> service1).build(); SftpSubsystemFactory f2 = builder.build(); assertNotSame("No new instance built", f1, f2); - assertSame("Mismatched executors", f1.getExecutorService(), f2.getExecutorService()); + assertSame("Mismatched executors", f1.resolveExecutorService(), f2.resolveExecutorService()); - SftpSubsystemFactory f3 = builder.withExecutorService(dummyExecutor()).build(); - assertNotSame("Executor service not changed", f1.getExecutorService(), f3.getExecutorService()); + CloseableExecutorService service2 = dummyExecutor(); + SftpSubsystemFactory f3 = builder.withExecutorServiceProvider(() -> service2).build(); + assertNotSame("Executor service not changed", f1.resolveExecutorService(), f3.resolveExecutorService()); } private static CloseableExecutorService dummyExecutor() {