SFTP Outbound Gateway

SFTP 传入网关提供了一组有限的命令,可用于与远程 SFTP 服务器进行交互:

The SFTP outbound gateway provides a limited set of commands that let you interact with a remote SFTP server:

  • ls (list files)

  • nlst (list file names)

  • get (retrieve a file)

  • mget (retrieve multiple files)

  • rm (remove file(s))

  • mv (move and rename file)

  • put (send a file)

  • mput (send multiple files)

Using the ls Command

ls 列出远程文件并支持以下选项:

ls lists remote files and supports the following options:

  • -1: Retrieve a list of filenames. The default is to retrieve a list of FileInfo objects

  • -a: Include all files (including those starting with '.')

  • -f: Do not sort the list

  • -dirs: Include directories (excluded by default)

  • -links: Include symbolic links (excluded by default)

  • -R: List the remote directory recursively

此外,文件名筛选以与 inbound-channel-adapter 相同的方式提供。

In addition, filename filtering is provided in the same manner as the inbound-channel-adapter.

ls 操作产生的消息负载是一份文件名列表或 FileInfo 对象列表(取决于您是否使用 -1 开关)。这些对象提供修改时间、权限等信息。

The message payload resulting from an ls operation is a list of file names or a list of FileInfo objects (depending on whether you usr the -1 switch). These objects provide information such as modified time, permissions, and others.

ls 命令操作的远程目录在 file_remoteDirectory 标头中提供。

The remote directory that the ls command acted on is provided in the file_remoteDirectory header.

使用递归选项 (-R) 时,fileName 包括任何子目录元素,并表示文件相对于远程目录的相对路径。如果您使用 -dirs 选项,每个递归目录也会作为列表中的元素返回。在这种情况下,我们建议您不要使用 -1 选项,因为您无法像使用 FileInfo 对象那样区分文件与目录。

When using the recursive option (-R), the fileName includes any subdirectory elements and represents the relative path to the file (relative to the remote directory). If you use the -dirs option, each recursive directory is also returned as an element in the list. In this case, we recommend that you not use the -1 option, because you would not be able to distinguish files from directories, which you can do when you use FileInfo objects.

如果要列出的远程路径以 “/” 符号开头,则 SFTP 将其视为绝对路径;如果没有 “/” 则视为当前用户主目录中的相对路径。

If remote path to list starts with a / symbol, it is treated by SFTP as an absolute path; without - as a relative path in the current user home.

Using nlst Command

版本 5 引入了对 nlst 命令的支持。

Version 5 introduced support for the nlst command.

nlst 列出远程文件名并仅支持一个选项:

nlst lists remote file names and supports only one option:

  • -f: Do not sort the list

nlst 操作返回的消息有效负载是一个文件名列表。

The message payload resulting from an nlst operation is a list of file names.

file_remoteDirectory 标头包含 nlst 命令对其执行操作的远程目录。

The file_remoteDirectory header holds the remote directory on which the nlst command acted.

SFTP 协议无法列出名称。该命令与带有 -1 选项的 ls 命令等效,并且为方便添加在这里。

The SFTP protocol does not provide the ability to list names. This command is the equivalent of the ls command with the -1 option and is added here for convenience.

Using the get Command

get 检索远程文件并支持以下选项:

get retrieves a remote file and supports the following options:

  • -P: Preserve the timestamp of the remote file.

  • -stream: Retrieve the remote file as a stream.

  • -D: Delete the remote file after successful transfer. The remote file is not deleted if the transfer is ignored, because the FileExistsMode is IGNORE and the local file already exists.

file_remoteDirectory 标头包含远程目录,file_remoteFile 标头包含文件名。

The file_remoteDirectory header holds the remote directory, and the file_remoteFile header holds the filename.

由`get`操作产生的消息有效负载是一个表示已检索文件的`File`对象。如果您使用`-stream`选项,则有效负载将是`InputStream`,而不是`File`。对于文本文件,一种常见用例是将此操作与file splitterstream transformer结合使用。在将远程文件作为流使用时,您应负责在使用流后关闭`Session`。为方便起见,`Session`在`closeableResource`标头中提供,而`IntegrationMessageHeaderAccessor`提供便利方法:

The message payload resulting from a get operation is a File object representing the retrieved file. If you use the -stream option, the payload is an InputStream rather than a File. For text files, a common use case is to combine this operation with a file splitter or a stream transformer. When consuming remote files as streams, you are responsible for closing the Session after the stream is consumed. For convenience, the Session is provided in the closeableResource header, and IntegrationMessageHeaderAccessor offers convenience method:

Closeable closeable = new IntegrationMessageHeaderAccessor(message).getCloseableResource();
if (closeable != null) {
    closeable.close();
}

框架组件(如File SplitterStream Transformer)在数据传输后自动关闭会话。

Framework components, such as the File Splitter and Stream Transformer, automatically close the session after the data is transferred.

以下示例显示了如何将文件作为流使用:

The following example shows how to consume a file as a stream:

<int-sftp:outbound-gateway session-factory="ftpSessionFactory"
                            request-channel="inboundGetStream"
                            command="get"
                            command-options="-stream"
                            expression="payload"
                            remote-directory="ftpTarget"
                            reply-channel="stream" />

<int-file:splitter input-channel="stream" output-channel="lines" />

如果您在自定义组件中消耗输入流,那么必须关闭 Session。可以将其用于自定义代码或者将消息的副本路由到 service-activator 并使用 SpEL,如下面的示例所示:

If you consume the input stream in a custom component, you must close the Session. You can either do that in your custom code or route a copy of the message to a service-activator and use SpEL, as the following example shows:

<int:service-activator input-channel="closeSession"
    expression="headers['closeableResource'].close()" />

Using the mget Command

mget 根据模式检索多个远程文件,并支持以下选项:

mget retrieves multiple remote files based on a pattern and supports the following options:

  • -P: Preserve the timestamps of the remote files.

  • -R: Retrieve the entire directory tree recursively.

  • -x: Throw an exception if no files match the pattern (otherwise, an empty list is returned).

  • -D: Delete each remote file after successful transfer. If the transfer is ignored, the remote file is not deleted, because the FileExistsMode is IGNORE and the local file already exists.

mget 操作产生的消息有效负载是一个 List<File> 对象(即,一个 File 对象的 List,每个文件都表示一个已检索的文件)。

The message payload resulting from an mget operation is a List<File> object (that is, a List of File objects, each representing a retrieved file).

从 5.0 版本开始,如果 FileExistsModeIGNORE,输出消息的有效负载不再包含由于文件已存在而不提取的文件。以前,数组包含所有文件,包括已存在的文件。

Starting with version 5.0, if the FileExistsMode is IGNORE, the payload of the output message no longer contain files that were not fetched due to the file already existing. Previously, the array contained all files, including those that already existed.

您用来确定远程路径的表达式应产生一个以 结尾的结果,例如 myfiles/ 获取 myfiles 下的完整树。

The expression you use determine the remote path should produce a result that ends with for example myfiles/ fetches the complete tree under myfiles.

从版本 5.0 开始,你可以使用递归 MGET,结合 FileExistsMode.REPLACE_IF_MODIFIED 模式,定期将整个远程目录树在本地同步。无论 -P(保留时间戳)选项如何,此模式都将本地文件的最后修改时间戳设置为远程文件的时间戳。

Starting with version 5.0, you can use a recursive MGET, combined with the FileExistsMode.REPLACE_IF_MODIFIED mode, to periodically synchronize an entire remote directory tree locally. This mode sets the local file’s last modified timestamp to the remote file’s timestamp, regardless of the -P (preserve timestamp) option.

Example 1. Notes for when using recursion (-R)

模式会被忽略,而假定为 *。默认情况下,将检索整个远程树。但是,您可以通过提供 FileListFilter 来筛选树中的文件。您还可以通过此方式筛选树中的目录。可以通过引用或 filename-patternfilename-regex 属性来提供 FileListFilter。例如,filename-regex="(subDir|.*1.txt)" 检索远程目录中所有以 1.txt 结尾的文件和子目录 subDir。但是,我们将在本注释之后描述另一种可用的替代方案。

The pattern is ignored and * is assumed. By default, the entire remote tree is retrieved. However, you can filter files in the tree by providing a FileListFilter. You can also filter directories in the tree this way. A FileListFilter can be provided by reference or by filename-pattern or filename-regex attributes. For example, filename-regex="(subDir|.*1.txt)" retrieves all files ending with 1.txt in the remote directory and the subdirectory subDir. However, we describe an alternative available after this note.

如果您筛选一个子目录,则不会对该子目录执行任何其他遍历。

If you filter a subdirectory, no additional traversal of that subdirectory is performed.

不允许 -dirs 选项(递归 mget 使用递归 ls 获得目录树,而目录本身不能包含在列表中)。

The -dirs option is not allowed (the recursive mget uses the recursive ls to obtain the directory tree and the directories themselves cannot be included in the list).

通常,您会在 local-directory-expression 中使用 #remoteDirectory 变量,以便在本地保留远程目录结构。

Typically, you would use the #remoteDirectory variable in the local-directory-expression so that the remote directory structure is retained locally.

持久的过滤文件列表现在有一个布尔属性 forRecursion。将此属性设置为 true,还将设置 alwaysAcceptDirectories,这意味着出站网关(lsmget)上的递归操作现在将始终在每次遍历完整目录树。这是为了解决目录树中深处更改未被检测到的问题。此外,forRecursion=true 会导致使用文件的完整路径作为元数据存储键;这解决了在不同目录中多次出现具有相同名称的文件时过滤器无法正常工作的问题。重要提示:这意味着无法在顶级目录下的文件找到持久元数据存储中的现有键。因此,该属性默认为 false;这可能会在未来版本中更改。

The persistent file list filters now have a boolean property forRecursion. Setting this property to true, also sets alwaysAcceptDirectories, which means that the recursive operation on the outbound gateways (ls and mget) will now always traverse the full directory tree each time. This is to solve a problem where changes deep in the directory tree were not detected. In addition, forRecursion=true causes the full path to files to be used as the metadata store keys; this solves a problem where the filter did not work properly if a file with the same name appears multiple times in different directories. IMPORTANT: This means that existing keys in a persistent metadata store will not be found for files beneath the top level directory. For this reason, the property is false by default; this may change in a future release.

从版本 5.0 开始,你可以将 SftpSimplePatternFileListFilterSftpRegexPatternFileListFilter 配置为始终通过目录,方法是将 alwaysAcceptDirectories 设置为 true。这样做允许对简单模式进行递归,如下面的示例所示:

Starting with version 5.0, you can configure the SftpSimplePatternFileListFilter and SftpRegexPatternFileListFilter to always pass directories by setting the alwaysAcceptDirectorties to true. Doing so allows recursion for a simple pattern, as the following examples show:

<bean id="starDotTxtFilter"
            class="org.springframework.integration.sftp.filters.SftpSimplePatternFileListFilter">
    <constructor-arg value="*.txt" />
    <property name="alwaysAcceptDirectories" value="true" />
</bean>

<bean id="dotStarDotTxtFilter"
            class="org.springframework.integration.sftp.filters.SftpRegexPatternFileListFilter">
    <constructor-arg value="^.*\.txt$" />
    <property name="alwaysAcceptDirectories" value="true" />
</bean>

您可以使用网关上的 filter 属性提供其中一个过滤器。

You can provide one of these filters by using the filter property on the gateway.

Using the put Command

put`将文件发送到远程服务器。消息的有效负载可以是`java.io.Filebyte[]`或`Stringremote-filename-generator(或表达式)用于命名远程文件。其他可用的属性包括`remote-directory`、temporary-remote-directory`及其*-expression`等价物:use-temporary-file-name`和`auto-create-directory。有关更多信息,请参阅 schema documentation

put sends a file to the remote server. The payload of the message can be a java.io.File, a byte[], or a String. A remote-filename-generator (or expression) is used to name the remote file. Other available attributes include remote-directory, temporary-remote-directory and their *-expression equivalents: use-temporary-file-name and auto-create-directory. See the schema documentation for more information.

put 操作产生的消息有效负载是一个 String,其中包含文件在服务器上的完整路径,以便在传输后使用。

The message payload resulting from a put operation is a String that contains the full path of the file on the server after transfer.

版本 4.3 引入了 chmod 属性,它在上载后更改远程文件权限。你可以使用传统的 Unix 八进制格式(例如,600 仅允许文件所有者读写)。在使用 Java 配置适配器时,你可以使用 setChmod(0600)

Version 4.3 introduced the chmod attribute, which changes the remote file permissions after upload. You can use the conventional Unix octal format (for example, 600 allows read-write for the file owner only). When configuring the adapter using java, you can use setChmod(0600).

Using the mput Command

mput 将多个文件发送到服务器,并支持以下选项:

mput sends multiple files to the server and supports the following option:

  • -R: Recursive — send all files (possibly filtered) in the directory and subdirectories

消息负载必须是一个表示本地目录的 java.io.File(或 String)。自 5.1 版本起,也支持一个 FileString 集合。

The message payload must be a java.io.File (or String) that represents a local directory. Since version 5.1, a collection of File or String is also supported.

支持与 xref:sftp/outbound-gateway.adoc#sftp-put-command[put 命令相同的属性。此外,你还可以使用 mput-patternmput-regexmput-filtermput-filter-expression 中的一个过滤本地目录中的文件。只要子目录本身通过过滤器,该过滤器就会使用递归。不通过过滤器的子目录不会进行递归。

The same attributes as the put command are supported. In addition, you can filter files in the local directory with one of mput-pattern, mput-regex, mput-filter, or mput-filter-expression. The filter works with recursion, as long as the subdirectories themselves pass the filter. Subdirectories that do not pass the filter are not recursed.

mput 操作产生的消息有效负载是一个 List<String> 对象(即,传输产生的远程文件路径的一个 List)。

The message payload resulting from an mput operation is a List<String> object (that is, a List of remote file paths resulting from the transfer).

版本 4.3 引入了 chmod 属性,它允许在上载后更改远程文件权限。你可以使用传统的 Unix 八进制格式(例如,600 仅允许文件所有者读写)。在使用 Java 配置适配器时,你可以使用 setChmodOctal("600")setChmod(0600)

Version 4.3 introduced the chmod attribute, which lets you change the remote file permissions after upload. You can use the conventional Unix octal format (for example, 600 allows read-write for the file owner only). When configuring the adapter with Java, you can use setChmodOctal("600") or setChmod(0600).

Using the rm Command

rm 命令没有选项。

The rm command has no options.

如果删除操作成功,则产生的消息有效负载为 Boolean.TRUE。否则,消息有效负载为 Boolean.FALSEfile_remoteDirectory 标头包含远程目录,file_remoteFile 标头包含文件名。

If the remove operation was successful, the resulting message payload is Boolean.TRUE. Otherwise, the message payload is Boolean.FALSE. The file_remoteDirectory header holds the remote directory, and the file_remoteFile header holds the file name.

Using the mv Command

mv 命令没有选项。

The mv command has no options.

expression 属性定义 “from” 路径,而 rename-expression 属性定义 “to” 路径。默认情况下,rename-expressionheaders['file_renameTo']。此表达式不得计算为 null 或空 String。如有必要,将创建任何需要的远程目录。结果消息的有效内容是 Boolean.TRUEfile_remoteDirectory 标题保存原始远程目录,而 file_remoteFile 标题保存文件名。file_renameTo 标题保存新路径。

The expression attribute defines the “from” path, and the rename-expression attribute defines the “to” path. By default, the rename-expression is headers['file_renameTo']. This expression must not evaluate to null or an empty String. If necessary, any remote directories needed are created. The payload of the result message is Boolean.TRUE. The file_remoteDirectory header holds the original remote directory, and the file_remoteFile header holds the filename. The file_renameTo header holds the new path.

从 5.5.6 版本开始,remoteDirectoryExpression 可以方便地用于 mv 命令。如果“自”文件不是完整的文件路径,remoteDirectoryExpression 的结果将用作远程目录。对于“至”文件也是如此,例如,如果任务只是重命名某个目录中的远程文件。

Starting with version 5.5.6, the remoteDirectoryExpression can be used in the mv command for convenience. If the “from” file is not a full file path, the result of remoteDirectoryExpression is used as the remote directory. The same applies for the “to” file, for example, if the task is just to rename a remote file in some directory.

Additional Command Information

getmget 命令支持 local-filename-generator-expression 属性。它定义了一个 SpEL 表达式,以便在传输期间生成本地文件的名称。评估上下文的根对象是请求消息。还可以使用 remoteFileName 变量。对于 mget 来说特别有用(例如:local-filename-generator-expression="#remoteFileName.toUpperCase() + headers.foo")。

The get and mget commands support the local-filename-generator-expression attribute. It defines a SpEL expression to generate the names of local files during the transfer. The root object of the evaluation context is the request message. The remoteFileName variable is also available. It is particularly useful for mget (for example: local-filename-generator-expression="#remoteFileName.toUpperCase() + headers.foo").

getmget 命令支持 local-directory-expression 属性。它定义了一个 SpEL 表达式,以便在传输期间生成本地目录的名称。评估上下文的根对象是请求消息。还可以使用 remoteDirectory 变量。对于 mget 来说特别有用(例如:local-directory-expression="'/tmp/local/' + #remoteDirectory.toUpperCase() + headers.myheader")。此属性与 local-directory 属性互斥。

The get and mget commands support the local-directory-expression attribute. It defines a SpEL expression to generate the names of local directories during the transfer. The root object of the evaluation context is the request message. The remoteDirectory variable is also available. It is particularly useful for mget (for example: local-directory-expression="'/tmp/local/' + #remoteDirectory.toUpperCase() + headers.myheader"). This attribute is mutually exclusive with the local-directory attribute.

对于所有命令,网关的“expression”属性都保存了命令作用于其上的路径。对于 mget 命令,表达式可能计算为 ,这意味着要检索所有文件,somedirectory/ 和以 * 结尾的其他值。

For all commands, the 'expression' property of the gateway holds the path on which the command acts. For the mget command, the expression might evaluate to , meaning to retrieve all files, somedirectory/, and other values that end with *.

以下示例显示了为 ls 命令配置的网关:

The following example shows a gateway configured for an ls command:

<int-ftp:outbound-gateway id="gateway1"
        session-factory="ftpSessionFactory"
        request-channel="inbound1"
        command="ls"
        command-options="-1"
        expression="payload"
        reply-channel="toSplitter"/>

发送到 toSplitter 通道的消息的有效负载是一个 String 对象的列表,每个对象都包含一个文件名称。如果你省略了 command-options="-1",则有效负载将是 FileInfo 对象的列表。你可以将选项提供为用空格分隔的列表(例如,command-options="-1 -dirs -links")。

The payload of the message sent to the toSplitter channel is a list of String objects, each of which contains the name of a file. If you omitted command-options="-1", the payload would be a list of FileInfo objects. You can provide options as a space-delimited list (for example, command-options="-1 -dirs -links").

从版本 4.2 开始,GETMGETPUTMPUT 命令支持 FileExistsMode 属性(使用命名空间支持时为 mode)。这会影响本地文件存在 (GETMGET) 或远程文件存在 (PUTMPUT) 时的行为。支持的模式为 REPLACEAPPENDFAILIGNORE。为了向后兼容,PUTMPUT 操作的默认模式为 REPLACE。对于 GETMGET 操作,默认值为 FAIL

Starting with version 4.2, the GET, MGET, PUT, and MPUT commands support a FileExistsMode property (mode when using the namespace support). This affects the behavior when the local file exists (GET and MGET) or the remote file exists (PUT and MPUT). The supported modes are REPLACE, APPEND, FAIL, and IGNORE. For backwards compatibility, the default mode for PUT and MPUT operations is REPLACE. For GET and MGET operations, the default is FAIL.

Configuring with Java Configuration

以下 Spring Boot 应用程序展示了如何使用 Java 配置出站网关的示例:

The following Spring Boot application shows an example of how to configure the outbound gateway with Java:

@SpringBootApplication
public class SftpJavaApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(SftpJavaApplication.class)
            .web(false)
            .run(args);
    }

    @Bean
    @ServiceActivator(inputChannel = "sftpChannel")
    public MessageHandler handler() {
        return new SftpOutboundGateway(ftpSessionFactory(), "ls", "'my_remote_dir/'");
    }

}

Configuring with the Java DSL

以下 Spring Boot 应用程序显示了一个示例,演示如何使用 Java DSL 配置出站网关:

The following Spring Boot application shows an example of how to configure the outbound gateway with the Java DSL:

@SpringBootApplication
public class SftpJavaApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(SftpJavaApplication.class)
            .web(false)
            .run(args);
    }

    @Bean
    public SessionFactory<SftpClient.DirEntry> sftpSessionFactory() {
        DefaultSftpSessionFactory sf = new DefaultSftpSessionFactory();
        sf.setHost("localhost");
        sf.setPort(port);
        sf.setUsername("foo");
        sf.setPassword("foo");
        factory.setTestSession(true);
        return new CachingSessionFactory<>(sf);
    }

    @Bean
    public QueueChannelSpec remoteFileOutputChannel() {
        return MessageChannels.queue();
    }

    @Bean
    public IntegrationFlow sftpMGetFlow() {
        return IntegrationFlow.from("sftpMgetInputChannel")
            .handle(Sftp.outboundGateway(sftpSessionFactory(),
                            AbstractRemoteFileOutboundGateway.Command.MGET, "payload")
                    .options(AbstractRemoteFileOutboundGateway.Option.RECURSIVE)
                    .regexFileNameFilter("(subSftpSource|.*1.txt)")
                    .localDirectoryExpression("'myDir/' + #remoteDirectory")
                    .localFilenameExpression("#remoteFileName.replaceFirst('sftpSource', 'localTarget')"))
            .channel("remoteFileOutputChannel")
            .get();
    }

}

Outbound Gateway Partial Success (mget and mput)

在对多个文件执行操作(通过使用 mgetmput)时,在传输了一个或多个文件之后一段时间,可能会发生异常。在这种情况下(从版本 4.2 开始),会引发 PartialSuccessException。除了通常的 MessagingException 属性(failedMessagecause)之外,此异常还有两个附加属性:

When performing operations on multiple files (by using mget and mput) an exception can occur some time after one or more files have been transferred. In this case (starting with version 4.2), a PartialSuccessException is thrown. As well as the usual MessagingException properties (failedMessage and cause), this exception has two additional properties:

  • partialResults: The successful transfer results.

  • derivedInput: The list of files generated from the request message (such as local files to transfer for an mput).

这些属性使您可以确定哪些文件已成功传输,哪些文件尚未传输。

These attributes let you determine which files were successfully transferred and which were not.

在递归 mput 的情况下,PartialSuccessException 可能有嵌套的 PartialSuccessException 实例。

In the case of a recursive mput, the PartialSuccessException may have nested PartialSuccessException instances.

考虑以下目录结构:

Consider the following directory structure:

root/
|- file1.txt
|- subdir/
   | - file2.txt
   | - file3.txt
|- zoo.txt

如果异常发生在 file3.txt 上,则网关引发的 PartialSuccessException 具有 file1.txtsubdirzoo.txtderivedInput,以及 file1.txtpartialResults。其 cause 是另一个具有 file2.txtfile3.txtderivedInputfile2.txtpartialResultsPartialSuccessException

If the exception occurs on file3.txt, the PartialSuccessException thrown by the gateway has derivedInput of file1.txt, subdir, and zoo.txt and partialResults of file1.txt. Its cause is another PartialSuccessException with derivedInput of file2.txt and file3.txt and partialResults of file2.txt.