在Kotlin创建彩票3d字谜DSL

kotlin. 作为编程语言提供了一些非常强大的功能,可以创建自定义内部 域特定语言(DSL)。其中彩票3d字谜功能,我也写了关于这个博客,被称为 带接收器的功能文字,其他人是 调用公约 或者 infix表示法。在本文中,我将通过引入将DSL公开为其API的库来展示如何创建Kotlin DSL。当我必须建立时,我经常努力与Java的API斗争 SSL / TLS连接 在我的场景中需要实施 https. 沟通。我一直觉得想写彩票3d字谜可以为我提供这项任务的小图书馆,隐藏了所有困难,当然是它所需的样板。

域特定语言

术语 域特定语言 如今非常广泛地使用,但在我谈论的情况下,它会引用某种 “迷你语”。 它用于以半声明方式描述特定域对象的构造。 DSL的例子是 Groovy建设者 用于创建XML,HTML或UI数据。在我看来,最好的例子是构建工具 本身也使用基于Groovy的DSL来描述软件构建自动化 [1].
要简单地提出他们的目的,DSL是一种提供的方式 API. 这是更清洁的,更可读,更重要的是,比传统的API更具结构化。而不是必须在彩票3d字谜中呼叫各个函数 至关重要的 方式,DSLS使用嵌套描述,从而创建清洁结构;我们甚至可以称之为“语法”。 DSLS定义了相互作用的不同构造的可能性,并且还可以做出广泛的范围,其中可以使用不同的功能。

为什么Kotlin对DSLS特别好

正如我们已经知道的那样,Kotlin是一种静态类型的语言,它能够以Groovy * Duck Owes这样的动态类型语言不可用的功能*。最重要的是,静态键入允许在编译时出错检测,并且通常更好的IDE支持。

好的,让我们不要浪费时间与理论,并开始玩DSL的乐趣,这意味着很多嵌套的lambdas!所以,你最好知道如何使用 lambdas. in Kotlin 🙂

kotlin dsl by示例

如本帖子的介绍部分中已在此介绍中所述,我们将使用Java的API来设置SSL / TLS连接作为此处的示例。如果您不熟悉它,以下将提供简短的介绍。

Java安全套接字扩展

Java安全套接字扩展(JSSE) 是自1.4起是Java SE的一部分的图书馆。它提供了通过SSL / TLS创建安全连接的功能,包括客户端/服务器身份验证,数据加密, 和消息完整性。像许多其他人一样,我发现安全主题相当棘手,尽管我在日常工作中经常使用该功能。其中彩票3d字谜原因可能是可能的API组合的大量,另一种是建立这种连接所需的冗长。看看类层次结构:

jsse_classes.

我们可以看到相当多的课程,需要以一种有意义的方式组合。你经常通过创造彩票3d字谜开始的开始 相信 商店和A. 钥匙 store and use in combination with a random generator for setting up the SSLContext. The SSLContext is used for creating a SSLSocketFactory or SSLServerSocketFactory, which then provides the Socket instances. This sounds pretty easy but let's observe how it looks when expressed in Java code.

在Java中设置TLS连接

The abstractly described task of assembling the bits and pieces together took me little more than 100 lines of code. The following snippet shows a function that can be used to connect to a TLS server with optional mutual authentication, which is needed if both parties, client and server, need to trust each other. The classes can be found in the javax.net.ssl package.

public class TLSConfiguration { ... }
public class StoreType { ... }

 public void connectSSL(String host, int port,
        TLSConfiguration tlsConfiguration) throws IOException {

        String tlsVersion = tlsConfiguration.getProtocol();
        StoreType keystore = tlsConfiguration.getKeystore();
        StoreType trustStore = tlsConfiguration.getTruststore();
        try {
            SSLContext ctx = SSLContext.getInstance(tlsVersion);
            TrustManager[] tm = null;
            KeyManager[] km = null;
            if (trustStore != null) {
                tm = getTrustManagers(trustStore.getFilename(), 
                        trustStore.getPassword().toCharArray(),
                        trustStore.getStoretype(), trustStore.getAlgorithm());
            }
            if (keystore != null) {
                km = createKeyManagers(keystore.getFilename(), 
                        keystore.getPassword(),
                        keystore.getStoretype(), keystore.getAlgorithm());
            }
            ctx.init(km, tm, new SecureRandom());
            SSLSocketFactory sslSocketFactory = ctx.getSocketFactory();
            SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(
                                  host, port);
            sslSocket.startHandshake();
        } catch (Exception e) {
            throw new IllegalStateException("Not working :-(", e);
        }
    }


    private static TrustManager[] getTrustManagers(
        final String path, final char[] password,
        final String storeType, final String algorithm) throws Exception {

        TrustManagerFactory fac = TrustManagerFactory.getInstance(
               algorithm == null ? "SunX509" : algorithm);
        KeyStore ks = KeyStore.getInstance(
               storeType == null ? "JKS" : storeType);
        Path storeFile = Paths.get(path);
        ks.load(new FileInputStream(storeFile.toFile()), password);
        fac.init(ks);
        return fac.getTrustManagers();
    }

    private static KeyManager[] createKeyManagers(
        final String filename, final String password,
        final String keyStoreType, final String algorithm) throws Exception {

        KeyStore ks = KeyStore.getInstance(
                keyStoreType == null ? "PKCS12" : keyStoreType);
        ks.load(new FileInputStream(filename), password.toCharArray());
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(
                algorithm == null ? "SunX509" : algorithm);
        kmf.init(ks, password.toCharArray());
        return kmf.getKeyManagers();
    }

好的......这是Java,对吧?当然,它具有很多处理的检查异常和资源,需要管理,这是我在这里简洁的简化。作为下一步,让我们看看这看起来如何转换为Kotlin代码:

与Kotlin设置TLS连接

 fun connectSSL(host: String, port: Int, protocols: List<String>, kmConfig: Store?, tmConfig: Store?){
    val context = createSSLContext(protocols, kmConfig, tmConfig)
    val sslSocket = context.socketFactory.createSocket(host, port) as SSLSocket
    sslSocket.startHandshake()
}

fun createSSLContext(protocols: List<String>, kmConfig: Store?, tmConfig: Store?): SSLContext {
    if (protocols.isEmpty()) {
        throw IllegalArgumentException("At least one protocol must be provided.")
    }
    return SSLContext.getInstance(protocols[0]).apply {
        val keyManagerFactory = kmConfig?.let { conf ->
            val defaultAlgorithm = KeyManagerFactory.getDefaultAlgorithm()
            KeyManagerFactory.getInstance(conf.algorithm ?: defaultAlgorithm).apply {
                init(loadKeyStore(conf), conf.password)
            }
        }
        val trustManagerFactory = tmConfig?.let { conf ->
            val defaultAlgorithm = TrustManagerFactory.getDefaultAlgorithm()
            TrustManagerFactory.getInstance(conf.algorithm ?: defaultAlgorithm).apply {
                init(loadKeyStore(conf))
            }
        }

        init(keyManagerFactory?.keyManagers, trustManagerFactory?.trustManagers,
            SecureRandom())
    }
}

fun loadKeyStore(store: Store) = KeyStore.getInstance(store.fileType).apply {
    load(FileInputStream(store.name), store.password)
}

You might notice that the shown code is not a one-to-one conversion, which is because Kotlin provides a set of very useful functions in its standard library that often help with writing smarter code. This small piece of source code contains four usages of apply, a method that makes use of 带接收器的功能文字。这是Kotlin的着名之一 范围功能,它在任意上下文对象上创建了彩票3d字谜范围,其中我们在没有其他限定符的情况下访问该上下文对象的成员。

我们现在看到,Kotlin可以比Java更简洁,但这是昨天的新闻。在下一节中,我们最终会看到该代码如何在DSL中包装,然后将其作为API暴露给客户端。

在Kotlin创建彩票3d字谜DSL

创建API时的第一件事是在创建API时(以及这肯定也适用于DSL),是我们如何轻松实现客户端。我们需要定义用户需要提供的某些配置参数。
在我们的情况下,这很简单。我们需要零或彩票3d字谜描述彩票3d字谜 钥匙store. A. 信任学院 分别。此外,它很重要 密码套房 和套接字 连接超时 已知。最后但并非最不重要的是,它必须为我们的连接提供一组协议,这将是类似的 tlsv1.2. 例如。对于每个配置,可以使用值默认值,如果需要,将使用。
The described values can easily be wrapped into a configuration class, which we’ll call ProviderConfiguration since it will be used for configuring a TLSSocketFactoryProvider later on.

配置

class ProviderConfiguration {

    var kmConfig: Store? = null
    var tmConfig: Store? = null
    var socketConfig: SocketConfiguration? = null

    fun open(name: String) = Store(name)

    fun sockets(configInit: SocketConfiguration.() -> Unit) {
        this.socketConfig = SocketConfiguration().apply(configInit)
    }

    fun keyManager(store: () -> Store) {
        this.kmConfig = store()
    }

    fun trustManager(store: () -> Store) {
        this.tmConfig = store()
    }
}

We can see three nullable properties here, each of which is null 经过 default because the clients might not want to configure everything for their connection. The relevant methods in this class are sockets(), 钥匙Manager()相信Manager(), all of which have a single parameter of a function type. The sockets() method goes even a step further by defining a function literal 与接收者, which is SocketConfiguration here. This enables the client to pass in a lambda that has access to all members of SocketConfiguration as we know it from extension functions and shown with apply in earlier examples.
The socket() method 提供 the receiver by creating a new instance and then invoking the passed function on it with apply. The resulting configured instance is then used as a value for the internal property. Both other functions are a bit easier as they define simple functions types, without a receiver, as their parameters. They simply expect a provider of an instance of Store, which then is set on the internal property.

StoreSocketConfiguration

Here you can observce the classes StoreSocketConfiguration:

data class SocketConfiguration(
    var cipherSuites: List<String>? = null, 
    var timeout: Int? = null,
    var clientAuth: Boolean = false)

class Store(val name: String) {
    var algorithm: String? = null
    var password: CharArray? = null
    var fileType: String = "JKS"

    infix fun withPass(pass: String) = apply {
        password = pass.toCharArray()
    }

    infix fun beingA(type: String) = apply {
        fileType = type
    }

    infix fun using(algo: String) = apply {
        algorithm = algo
    }
}

The first one is as easy as it could get, a simple data class with, once again, nullable properties. Store is a bit unique though as it, in addition to its properties, defines three infix functions, which are acting as setters for the properties basically. We again make use of apply here because it returns its context object after invocation and is used as a tool for providing a fluent API here; the methods can be chained later on. One thing I haven’t mentioned so far is the open(name: String) function defined in ProviderConfiguration. This one is supposed to be used as a factory for instances ofStore 和 we are about to see this in action soon. All of this in combination creates a neat way for defining the necessary configuration data. Before we can have a look at the client side, it's necessary to observe theTLSSocketFactoryProvider, which has to be configured with the classes I just introduced.

kotlin. DSL核心

class TLSSocketFactoryProvider(init: ProviderConfiguration.() -> Unit) {

    private val config: ProviderConfiguration = ProviderConfiguration().apply(init)

    fun createSocketFactory(protocols: List): SSLSocketFactory =
        with(createSSLContext(protocols)) {
            return ExtendedSSLSocketFactory(
                socketFactory, protocols.toTypedArray(),
                getOptionalCipherSuites() ?: socketFactory.defaultCipherSuites
            )
        }

    fun createServerSocketFactory(protocols: List): SSLServerSocketFactory =
        with(createSSLContext(protocols)) {
            return ExtendedSSLServerSocketFactory(
                serverSocketFactory, protocols.toTypedArray(),
                getOptionalCipherSuites() ?: serverSocketFactory.defaultCipherSuites
            )
        }

    private fun getOptionalCipherSuites() =
        config.socketConfig?.cipherSuites?.toTypedArray()


    private fun createSSLContext(protocols: List<String>): SSLContext {
       //... already shown earlier
    }
}

This one isn’t hard to understand either. Most of the DSL's content has already been shown in createSSLContext() earlier.
The most important thing in this listing is the constructor. It expects a function with a ProviderConfiguration as a receiver. Internally it creates a new instance of it and calls this function in order to initialize the configuration. The configuration is used in TLSSocketFactoryProvider's other functions for setting up a SocketFactory as soon as one of the public methods createSocketFactory 或者 createServerSocketFactory is being called.

客户端API和DSL的使用

val defaultTLSProtocols = listOf("tlsv1.2.")

fun serverSocketFactory(
    protocols: List<String> = defaultTLSProtocols,
    configuration: ProviderConfiguration.() -> Unit = {}) =
        with(TLSSocketFactoryProvider(configuration)) {
            this.createServerSocketFactory(protocols)
        }

fun socketFactory(
    protocols: List<String> = defaultTLSProtocols,
    configuration: ProviderConfiguration.() -> Unit = {}) =
        with(TLSSocketFactoryProvider(configuration)) {
            this.createSocketFactory(protocols)
        }

In order to assemble all of this DSL together, simple top-level functions were created, which represent the client’s entry point to this DSL. These two functions only delegate a function literal with ProviderConfiguration receiver to a created instance of TLSSocketFactoryProvider, which is used to create corresponding socket and server socket factories via createSocketFactorycreateServerSocketFactory respectively.

最后,我们可以轻松使用此DSL并使用IT创建一些套接字:

 val fac = socketFactory {
        keyManager {
            open("certsandstores/clientkeystore") withPass "123456" beingA "jks"
        }
        trustManager {
            open("certsandstores/myTruststore") withPass "123456" beingA "jks"
        }
        sockets {
            cipherSuites =
            listOf("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
                    "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
                    "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
                    "TLS_DHE_RSA_WITH_AES_256_CBC_SHA")
            timeout = 10_000
        }
    }

 val socket = fac.createSocket("192.168.3.200", 9443)

Let’s recap: The top-level function socketFactory expects a lambda, which has access to ProviderConfiguration members since it’s the lambda’s receiver. Therefore we can call 钥匙Manager()相信Manager()sockets() without any additional prefix here. The functions 钥匙Manager()相信Manager() take an instance of Store, which we create by calling ProviderConfiguration::openStore's infix functions. The sockets() method is different as it expects a function literal with SocketConfiguration receiver, which is a data class and therefore provides access to its properties directly.

我希望这是可以理解的。完全了解Lambdas如何在Kotlin,特别是接收者的接收者工作是绝对不可避免的。

In my humble opinion, this is a very clear definition of a SocketFactory 和 much easier to understand than the standard Java way shown earlier. Another feature provided by a DSL like this is the possibility to make use of all language features and other methods that are available in the receiver’s contexts. You could easily read values from a file for creating the store configurations or use loops, ifwhen constructs etc. whenever you need to:

 val fac = socketFactory {
        trustManager {
            if (System.currentTimeMillis() % 2 == 0L) {
                open("any") withPass "123456" beingA "jks"
            } else {
                open("other") withPass "123456" beingA "jks"
            }
        }
    }

图书馆在github上

我们刚看过的代码可用 GitHub.。如果您有任何想法或疑虑,请告诉我。

对于您在TLS和JSSE Lib的专家的专家,特别是我知道,库还没有包含许多案例和JSSE的可能性。我希望能找到对这种图书馆感兴趣的人,以便我们可以找到相应扩展的方法。

包起来

我们已经看到为什么DSL可以通过使用JSSE库建立TLS连接的示例来为客户提供API的更好方法。 Kotlin是一种非常伟大的语言,用于编写此类API,因为其静态打字和巨大的功能。许多其他Kotlin DSL示例可用,您可以在GitHub上找到它们。作为起动器,看看 kotlin.test. 或者 kotlinx.html. for instance.

如果您希望查看我的示例甚至希望贡献,则该代码可在此处提供: 撒尿。随意提供任何反馈,我总是很乐意提供帮助。

如果您想了解更多关于Kotlin的美丽功能,我推荐这本书 kotlin在行动中 对你而且还要指导你到我的 其他文章 🙂


1。还有彩票3d字谜kotlin dsl for gradle: 译-Script-Kotlin

 

11 thoughts on “在Kotlin创建彩票3d字谜DSL

发表评论

您的电子邮件地址不会被公开。 必需的地方已做标记 *