diff --git a/.github/workflows/ci-4.x.yml b/.github/workflows/ci-4.x.yml index 8e9a77c5e67..654ba579c3e 100644 --- a/.github/workflows/ci-4.x.yml +++ b/.github/workflows/ci-4.x.yml @@ -17,7 +17,7 @@ jobs: jdk: 8 profile: '-PtestDomainSockets' - os: ubuntu-latest - jdk: 17 + jdk: 21 profile: '' - os: windows-latest jdk: 8 diff --git a/.github/workflows/ci-5.x.yml b/.github/workflows/ci-5.x.yml index d4f535707e8..c1950f65ea3 100644 --- a/.github/workflows/ci-5.x.yml +++ b/.github/workflows/ci-5.x.yml @@ -23,7 +23,7 @@ jobs: jdk: 11 profile: '-PtestDomainSockets' - os: ubuntu-latest - jdk: 17 + jdk: 21 profile: '' - os: windows-latest jdk: 11 diff --git a/README.md b/README.md index 98395fbe0ba..6ef9878f702 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,12 @@ Runs the tests > mvn test ``` +Tests can be run with specified HTTP port and/or HTTPS port. + +``` +> mvn test -Dvertx.httpPort=8888 -Dvertx.httpsPort=4044 +``` + Vert.x supports native transport on BSD and Linux, to run the tests with native transport ``` diff --git a/pom.xml b/pom.xml index 2cc0444a3cf..c4d58b5e9fd 100644 --- a/pom.xml +++ b/pom.xml @@ -195,7 +195,7 @@ com.aayushatharva.brotli4j brotli4j - 1.8.0 + 1.12.0 test @@ -416,7 +416,7 @@ org.apache.maven.plugins maven-failsafe-plugin - 2.19.1 + 3.2.5 ssl-engine:default @@ -479,6 +479,10 @@ io/vertx/it/SLF4JLogDelegateTest.java + + System.out + false + @@ -567,6 +571,29 @@ + + no-recycler-pool-jackson + + integration-test + verify + + + + io/vertx/it/NoRecyclerPoolJacksonTest.java + + + + com.fasterxml.jackson.core + jackson-core + 2.15.1 + + + + com.fasterxml.jackson.core:jackson-core + com.fasterxml.jackson.core:jackson-databind + + + no-jackson-databind diff --git a/src/main/asciidoc/buffers.adoc b/src/main/asciidoc/buffers.adoc index 109c84d4674..9422eb9fb74 100644 --- a/src/main/asciidoc/buffers.adoc +++ b/src/main/asciidoc/buffers.adoc @@ -35,7 +35,12 @@ Create a buffer from a String: The String will be encoded using the specified en {@link examples.BufferExamples#example3} ---- -include::override/buffer_from_bytes.adoc[] +Create a buffer from a byte[] + +[source,java] +---- +{@link examples.BufferExamples#example4} +---- Create a buffer with an initial size hint. If you know your buffer will have a certain amount of data written to it you can create the buffer and specify this size. This makes the buffer initially allocate that much memory and is diff --git a/src/main/asciidoc/cli-for-java.adoc b/src/main/asciidoc/cli-for-java.adoc deleted file mode 100644 index 8d3f8811423..00000000000 --- a/src/main/asciidoc/cli-for-java.adoc +++ /dev/null @@ -1,81 +0,0 @@ -=== Typed options and arguments - -The described {@link io.vertx.core.cli.Option} and {@link io.vertx.core.cli.Argument} classes are _untyped_, -meaning that the only get String values. -{@link io.vertx.core.cli.TypedOption} and {@link io.vertx.core.cli.TypedArgument} let you specify a _type_, so the -(String) raw value is converted to the specified type. - -Instead of -{@link io.vertx.core.cli.Option} and {@link io.vertx.core.cli.Argument}, use {@link io.vertx.core.cli.TypedOption} -and {@link io.vertx.core.cli.TypedArgument} in the {@link io.vertx.core.cli.CLI} definition: - -[source,java] ----- -{@link examples.cli.TypedCLIExamples#example1} ----- - -Then you can retrieve the converted values as follows: - -[source,java] ----- -{@link examples.cli.TypedCLIExamples#example2} ----- - -The vert.x CLI is able to convert to classes: - -* having a constructor with a single -{@link java.lang.String} argument, such as {@link java.io.File} or {@link io.vertx.core.json.JsonObject} -* with a static `from` or `fromString` method -* with a static `valueOf` method, such as primitive types and enumeration - -In addition, you can implement your own {@link io.vertx.core.cli.converters.Converter} and instruct the CLI to use -this converter: - -[source,java] ----- -{@link examples.cli.TypedCLIExamples#example3} ----- - -For booleans, the boolean values are evaluated to `true`: `on`, `yes`, `1`, `true`. - -If one of your option has an `enum` as type, it computes the set of choices automatically. - -=== Using annotations - -You can also define your CLI using annotations. Definition is done using annotation on the class and on _setter_ -methods: - -[source, java] ----- -@Name("some-name") -@Summary("some short summary.") -@Description("some long description") -public class AnnotatedCli { - - private boolean flag; - private String name; - private String arg; - - @Option(shortName = "f", flag = true) - public void setFlag(boolean flag) { - this.flag = flag; - } - - @Option(longName = "name") - public void setName(String name) { - this.name = name; - } - - @Argument(index = 0) - public void setArg(String arg) { - this.arg = arg; - } -} ----- - -Once annotated, you can define the {@link io.vertx.core.cli.CLI} and inject the values using: - -[source,java] ----- -{@link examples.cli.TypedCLIExamples#example4} ----- diff --git a/src/main/asciidoc/cli.adoc b/src/main/asciidoc/cli.adoc deleted file mode 100644 index c2036876423..00000000000 --- a/src/main/asciidoc/cli.adoc +++ /dev/null @@ -1,180 +0,0 @@ -Vert.x Core provides an API for parsing command line arguments passed to programs. It's also able to print help -messages detailing the options available for a command line tool. Even if such features are far from -the Vert.x core topics, this API is used in the {@link io.vertx.core.Launcher} class that you can use in _fat-jar_ -and in the `vertx` command line tools. In addition, it's polyglot (can be used from any supported language) and is -used in Vert.x Shell. - -Vert.x CLI provides a model to describe your command line interface, but also a parser. This parser supports -different types of syntax: - -* POSIX like options (ie. `tar -zxvf foo.tar.gz`) -* GNU like long options (ie. `du --human-readable --max-depth=1`) -* Java like properties (ie. `java -Djava.awt.headless=true -Djava.net.useSystemProxies=true Foo`) -* Short options with value attached (ie. `gcc -O2 foo.c`) -* Long options with single hyphen (ie. `ant -projecthelp`) - -Using the CLI api is a 3-steps process: - -1. The definition of the command line interface -2. The parsing of the user command line -3. The query / interrogation - -=== Definition Stage - -Each command line interface must define the set of options and arguments that will be used. It also requires a -name. The CLI API uses the {@link io.vertx.core.cli.Option} and {@link io.vertx.core.cli.Argument} classes to -describe options and arguments: - -[source,$lang] ----- -{@link examples.cli.CLIExamples#example1} ----- - -As you can see, you can create a new {@link io.vertx.core.cli.CLI} using -{@link io.vertx.core.cli.CLI#create(java.lang.String)}. The passed string is the name of the CLI. Once created you -can set the summary and description. The summary is intended to be short (one line), while the description can -contain more details. Each option and argument are also added on the `CLI` object using the -{@link io.vertx.core.cli.CLI#addArgument(io.vertx.core.cli.Argument)} and -{@link io.vertx.core.cli.CLI#addOption(io.vertx.core.cli.Option)} methods. - -==== Options - -An {@link io.vertx.core.cli.Option} is a command line parameter identified by a _key_ present in the user command -line. Options must have at least a long name or a short name. Long name are generally used using a `--` prefix, -while short names are used with a single `-`. Names are case-sensitive; however, case-insensitive name matching -will be used during the <> if no exact match is found. -Options can get a description displayed in the usage (see below). Options can receive 0, 1 or several values. An -option receiving 0 values is a `flag`, and must be declared using -{@link io.vertx.core.cli.Option#setFlag(boolean)}. By default, options receive a single value, however, you can -configure the option to receive several values using {@link io.vertx.core.cli.Option#setMultiValued(boolean)}: - -[source,$lang] ----- -{@link examples.cli.CLIExamples#example2} ----- - -Options can be marked as mandatory. A mandatory option not set in the user command line throws an exception during -the parsing: - -[source,$lang] ----- -{@link examples.cli.CLIExamples#example3} ----- - -Non-mandatory options can have a _default value_. This value would be used if the user does not set the option in -the command line: - -[source,$lang] ----- -{@link examples.cli.CLIExamples#example4} ----- - -An option can be _hidden_ using the {@link io.vertx.core.cli.Option#setHidden(boolean)} method. Hidden option are -not listed in the usage, but can still be used in the user command line (for power-users). - -If the option value is constrained to a fixed set, you can set the different acceptable choices: - -[source,$lang] ----- -{@link examples.cli.CLIExamples#example41} ----- - -Options can also be instantiated from their JSON form. - -==== Arguments - -Unlike options, arguments do not have a _key_ and are identified by their _index_. For example, in -`java com.acme.Foo`, `com.acme.Foo` is an argument. - -Arguments do not have a name, there are identified using a 0-based index. The first parameter has the -index `0`: - -[source,$lang] ----- -{@link examples.cli.CLIExamples#example5} ----- - -If you don't set the argument indexes, it computes it automatically by using the declaration order. - -[source,$lang] ----- -{@link examples.cli.CLIExamples#example51} ----- - -The `argName` is optional and used in the usage message. - -As options, {@link io.vertx.core.cli.Argument} can: - -* be hidden using {@link io.vertx.core.cli.Argument#setHidden(boolean)} -* be mandatory using {@link io.vertx.core.cli.Argument#setRequired(boolean)} -* have a default value using {@link io.vertx.core.cli.Argument#setDefaultValue(java.lang.String)} -* receive several values using {@link io.vertx.core.cli.Argument#setMultiValued(boolean)} - only the last argument -can be multi-valued. - -Arguments can also be instantiated from their JSON form. - -==== Usage generation - -Once your {@link io.vertx.core.cli.CLI} instance is configured, you can generate the _usage_ message: - -[source,$lang] ----- -{@link examples.cli.CLIExamples#example6} ----- - -It generates a usage message like this one: - -[source] ----- -Usage: copy [-R] source target - -A command line interface to copy files. - - -R,--directory enables directory support ----- - -If you need to tune the usage message, check the {@link io.vertx.core.cli.UsageMessageFormatter} class. - -=== Parsing Stage - -Once your {@link io.vertx.core.cli.CLI} instance is configured, you can parse the user command line to evaluate -each option and argument: - -[source,$lang] ----- -{@link examples.cli.CLIExamples#example7} ----- - -The {@link io.vertx.core.cli.CLI#parse(java.util.List)} method returns a {@link io.vertx.core.cli.CommandLine} -object containing the values. By default, it validates the user command line and checks that each mandatory options -and arguments have been set as well as the number of values received by each option. You can disable the -validation by passing `false` as second parameter of {@link io.vertx.core.cli.CLI#parse(java.util.List,boolean)}. -This is useful if you want to check an argument or option is present even if the parsed command line is invalid. - -You can check whether or not the -{@link io.vertx.core.cli.CommandLine} is valid using {@link io.vertx.core.cli.CommandLine#isValid()}. - -[[query_interrogation_stage]] -=== Query / Interrogation Stage - -Once parsed, you can retrieve the values of the options and arguments from the -{@link io.vertx.core.cli.CommandLine} object returned by the {@link io.vertx.core.cli.CLI#parse(java.util.List)} -method: - -[source,$lang] ----- -{@link examples.cli.CLIExamples#example8} ----- - -One of your options can be marked as "help". If a user command line enabled a "help" option, the validation -won't fail, but you have the opportunity to check if the user asks for help: - -[source,$lang] ----- -{@link examples.cli.CLIExamples#example9} ----- - -[language,java] ----- -include::cli-for-java.adoc[] ----- diff --git a/src/main/asciidoc/datagrams.adoc b/src/main/asciidoc/datagrams.adoc index b7418fe0b75..7c1285d4cb3 100644 --- a/src/main/asciidoc/datagrams.adoc +++ b/src/main/asciidoc/datagrams.adoc @@ -26,7 +26,7 @@ and NetClient (see above). === Creating a DatagramSocket -To use UDP you first need t create a {@link io.vertx.core.datagram.DatagramSocket}. It does not matter here if you only want to send data or send +To use UDP you first need to create a {@link io.vertx.core.datagram.DatagramSocket}. It does not matter here if you only want to send data or send and receive. [source,$lang] @@ -54,7 +54,7 @@ Sending packets is as easy as shown here: === Receiving Datagram packets If you want to receive packets you need to bind the {@link io.vertx.core.datagram.DatagramSocket} by calling -`listen(...)}` on it. +`listen(...)` on it. This way you will be able to receive {@link io.vertx.core.datagram.DatagramPacket}s that were sent to the address and port on which the {@link io.vertx.core.datagram.DatagramSocket} listens. diff --git a/src/main/asciidoc/dns.adoc b/src/main/asciidoc/dns.adoc index ae3791b309a..7e507ebaa9b 100644 --- a/src/main/asciidoc/dns.adoc +++ b/src/main/asciidoc/dns.adoc @@ -26,6 +26,8 @@ for non blocking address resolution. {@link examples.DNSExamples#example1__} ---- +A client uses a single event loop for querying purposes, it can safely be used from any thread, including non Vert.x thread. + === lookup Try to lookup the A (ipv4) or AAAA (ipv6) record for a given name. The first which is returned will be used, @@ -190,4 +192,35 @@ To do a reverse lookup for the ipaddress 10.0.0.1 do something similar like this {@link examples.DNSExamples#example15} ---- -include::override/dns.adoc[] +=== Error handling + +As you saw in previous sections the DnsClient allows you to pass in a Handler which will be notified with an +AsyncResult once the query was complete. In case of an error it will be notified with a DnsException which will +hole a {@link io.vertx.core.dns.DnsResponseCode} that indicate why the resolution failed. This DnsResponseCode +can be used to inspect the cause in more detail. + +Possible DnsResponseCodes are: + +- {@link io.vertx.core.dns.DnsResponseCode#NOERROR} No record was found for a given query +- {@link io.vertx.core.dns.DnsResponseCode#FORMERROR} Format error +- {@link io.vertx.core.dns.DnsResponseCode#SERVFAIL} Server failure +- {@link io.vertx.core.dns.DnsResponseCode#NXDOMAIN} Name error +- {@link io.vertx.core.dns.DnsResponseCode#NOTIMPL} Not implemented by DNS Server +- {@link io.vertx.core.dns.DnsResponseCode#REFUSED} DNS Server refused the query +- {@link io.vertx.core.dns.DnsResponseCode#YXDOMAIN} Domain name should not exist +- {@link io.vertx.core.dns.DnsResponseCode#YXRRSET} Resource record should not exist +- {@link io.vertx.core.dns.DnsResponseCode#NXRRSET} RRSET does not exist +- {@link io.vertx.core.dns.DnsResponseCode#NOTZONE} Name not in zone +- {@link io.vertx.core.dns.DnsResponseCode#BADVERS} Bad extension mechanism for version +- {@link io.vertx.core.dns.DnsResponseCode#BADSIG} Bad signature +- {@link io.vertx.core.dns.DnsResponseCode#BADKEY} Bad key +- {@link io.vertx.core.dns.DnsResponseCode#BADTIME} Bad timestamp + +All of those errors are "generated" by the DNS Server itself. + +You can obtain the DnsResponseCode from the DnsException like: + +[source,java] +---- +{@link examples.DNSExamples#example16} +---- diff --git a/src/main/asciidoc/eventbus.adoc b/src/main/asciidoc/eventbus.adoc index c94ab27b483..1dd62b18f41 100644 --- a/src/main/asciidoc/eventbus.adoc +++ b/src/main/asciidoc/eventbus.adoc @@ -175,7 +175,15 @@ You can send a message with {@link io.vertx.core.eventbus.EventBus#send}. {@link examples.EventBusExamples#example6} ---- -include::override/eventbus_headers.adoc[] +==== Setting headers on messages + +Messages sent over the event bus can also contain headers. This can be specified by providing a +{@link io.vertx.core.eventbus.DeliveryOptions} when sending or publishing: + +[source,$lang] +---- +{@link examples.EventBusExamples#headers(io.vertx.core.eventbus.EventBus)} +---- ==== Message ordering @@ -249,33 +257,62 @@ Message sends can fail for other reasons, including: In all cases, the reply handler will be called with the specific failure. -include::override/eventbus.adoc[] +==== Message Codecs -==== Clustered Event Bus +You can send any object you like across the event bus if you define and register a {@link io.vertx.core.eventbus.MessageCodec message codec} for it. -The event bus doesn't just exist in a single Vert.x instance. By clustering different Vert.x instances together on -your network they can form a single, distributed event bus. +Message codecs have a name and you specify that name in the {@link io.vertx.core.eventbus.DeliveryOptions} +when sending or publishing the message: -==== Clustering programmatically +[source,java] +---- +{@link examples.EventBusExamples#example10} +---- -If you're creating your Vert.x instance programmatically you get a clustered event bus by configuring the Vert.x -instance as clustered; +If you always want the same codec to be used for a particular type then you can register a default codec for it, then +you don't have to specify the codec on each send in the delivery options: -[source,$lang] +[source,java] ---- -{@link examples.EventBusExamples#example12} +{@link examples.EventBusExamples#example11} ---- -You should also make sure you have a {@link io.vertx.core.spi.cluster.ClusterManager} implementation on your classpath, for example the Hazelcast cluster manager. +You unregister a message codec with {@link io.vertx.core.eventbus.EventBus#unregisterCodec}. + +Message codecs don't always have to encode and decode as the same type. For example you can write a codec that +allows a MyPOJO class to be sent, but when that message is sent to a handler it arrives as a MyOtherPOJO class. + +Vert.x has built-in codecs for certain data types: + +- basic types (string, byte array, byte, int, long, double, boolean, short, char), or +- some Vert.x data types (buffers, JSON array, JSON objects), or +- types implementing the {@link io.vertx.core.shareddata.ClusterSerializable} interface, or +- types implementing the `java.io.Serializable` interface. + +[IMPORTANT] +==== +In clustered mode, {@link io.vertx.core.shareddata.ClusterSerializable} and `java.io.Serializable` objects are rejected by default, for security reasons. + +You can define which classes are allowed for encoding and decoding by providing functions which inspect the name of the class: + +- {@link io.vertx.core.eventbus.EventBus#clusterSerializableChecker EventBus.clusterSerializableChecker()}, and +- {@link io.vertx.core.eventbus.EventBus#serializableChecker EventBus.serializableChecker()}. +==== + +==== Clustered Event Bus -==== Clustering on the command line +The event bus doesn't just exist in a single Vert.x instance. +By clustering different Vert.x instances together on your network they can form a single, distributed event bus. -You can run Vert.x clustered on the command line with +If you're creating your Vert.x instance programmatically, you get a clustered event bus by configuring the Vert.x instance as clustered: +[source,$lang] ---- -vertx run my-verticle.js -cluster +{@link examples.EventBusExamples#example12} ---- +You should also make sure you have one of the link:/docs/#clustering[{@link io.vertx.core.spi.cluster.ClusterManager} implementations] on your classpath. + === Automatic clean-up in verticles If you're registering event bus handlers from inside verticles, those handlers will be automatically unregistered @@ -283,4 +320,27 @@ when the verticle is undeployed. == Configuring the event bus -include::override/configuring-eventbus.adoc[] +The event bus can be configured.It is particularly useful when the event bus is clustered. +Under the hood the event bus uses TCP connections to send and receive messages, so the {@link io.vertx.core.eventbus.EventBusOptions} let you configure all aspects of these TCP connections. +As the event bus acts as a server and client, the configuration is close to {@link io.vertx.core.net.NetClientOptions} and {@link io.vertx.core.net.NetServerOptions}. + +[source,$lang] +---- +{@link examples.EventBusExamples#example13} +---- + +The previous snippet depicts how you can use SSL connections for the event bus, instead of plain TCP connections. + +WARNING: To enforce the security in clustered mode, you **must** configure the cluster manager to use encryption or enforce security. +Refer to the documentation of the cluster manager for further details. + +The event bus configuration needs to be consistent in all the cluster nodes. + +The {@link io.vertx.core.eventbus.EventBusOptions} also lets you specify whether the event bus is clustered, the port and host. + +When used in containers, you can also configure the public host and port: + +[source,$lang] +---- +{@link examples.EventBusExamples#example14} +---- diff --git a/src/main/asciidoc/http.adoc b/src/main/asciidoc/http.adoc index 0091400f385..6a09ec1dcf3 100644 --- a/src/main/asciidoc/http.adoc +++ b/src/main/asciidoc/http.adoc @@ -59,6 +59,24 @@ The settings define how the client can use the connection, the default initial s - {@link io.vertx.core.http.Http2Settings#getMaxConcurrentStreams}: `100` as recommended by the HTTP/2 RFC - the default HTTP/2 settings values for the others +=== Configuring server supported HTTP versions + +Default supported HTTP versions depends on the server configuration. + +- when TLS is disabled + - HTTP/1.1, HTTP/1.0 + - HTTP/2 when {@link io.vertx.core.http.HttpServerOptions#isHttp2ClearTextEnabled} is `true` +- when TLS is enabled and ALPN disabled + - HTTP/1.1 and HTTP/1.0 +- when TLS is enabled and ALPN enabled + - the protocols defined by {@link io.vertx.core.http.HttpServerOptions#getAlpnVersions}: by default HTTP/1.1 and HTTP/2 + +If you want to disable HTTP/2 on the server +- when TLS is disabled, set {@link io.vertx.core.http.HttpServerOptions#setHttp2ClearTextEnabled} to `false` +- when TLS is enabled + - set ({@link io.vertx.core.http.HttpServerOptions#isUseAlpn}) to `false` + - _or_ remove HTTP/2 from the {@link io.vertx.core.http.HttpServerOptions#getAlpnVersions} list + === Logging network server activity For debugging purposes, network activity can be logged. @@ -182,9 +200,9 @@ It also has case-insensitive keys, that means you can do the following: {@link examples.HTTPExamples#example8} ---- -==== Request host +==== Request authority -Use {@link io.vertx.core.http.HttpServerRequest#host} to return the host of the HTTP request. +Use {@link io.vertx.core.http.HttpServerRequest#authority} to return the authority of the HTTP request. For HTTP/1.x requests the `host` header is returned, for HTTP/1 requests the `:authority` pseudo header is returned. @@ -491,9 +509,9 @@ calling write with a string or buffer followed by calling end with no arguments. {@link examples.HTTPExamples#example20} ---- -==== Closing the underlying connection +==== Closing the underlying TCP connection -You can close the underlying TCP connection with {@link io.vertx.core.http.HttpServerResponse#close}. +You can close the underlying TCP connection with {@link io.vertx.core.http.HttpConnection#close}. Non keep-alive connections will be automatically closed by Vert.x when the response is ended. @@ -562,7 +580,7 @@ Or use {@link io.vertx.core.http.HttpServerResponse#putTrailer}. If you were writing a web server, one way to serve a file from disk would be to open it as an {@link io.vertx.core.file.AsyncFile} and pipe it to the HTTP response. -Or you could load it it one go using {@link io.vertx.core.file.FileSystem#readFile} and write it straight to the response. +Or you could load it in one go using {@link io.vertx.core.file.FileSystem#readFile} and write it straight to the response. Alternatively, Vert.x provides a method which allows you to serve a file from disk or the filesystem to an HTTP response in one operation. @@ -580,7 +598,7 @@ Here's a very simple web server that serves files from the file system using sen ---- Sending a file is asynchronous and may not complete until some time after the call has returned. If you want to -be notified when the file has been written you can use {@link io.vertx.core.http.HttpServerResponse#sendFile(String)} +be notified when the file has been written you can use {@link io.vertx.core.http.HttpServerResponse#sendFile(String)}. Please see the chapter about <> for restrictions about the classpath resolution or disabling it. @@ -850,6 +868,26 @@ When a clients connects to an HTTP/2 server, it sends to the server its {@link i The settings define how the server can use the connection, the default initial settings for a client are the default values defined by the HTTP/2 RFC. +=== Pool configuration + + For performance purpose, the client uses connection pooling when interacting with HTTP/1.1 servers. The pool creates up + to 5 connections per server. You can override the pool configuration like this: + + [source,$lang] + ---- + {@link examples.HTTPExamples#examplePoolConfiguration} + ---- + + You can configure various pool {@link io.vertx.core.http.PoolOptions options} as follows + +- {@link io.vertx.core.http.PoolOptions options#setHttp1MaxSize} the maximum number of opened per HTTP/1.x server (5 by default) +- {@link io.vertx.core.http.PoolOptions options#setHttp2MaxSize} the maximum number of opened per HTTP/2 server (1 by default), you *should* not change this value since a single HTTP/2 connection is capable of delivering the same performance level than multiple HTTP/1.x connections +- {@link io.vertx.core.http.PoolOptions options#setCleanerPeriod} the period in milliseconds at which the pool checks expired connections (1 second by default) +- {@link io.vertx.core.http.PoolOptions options#setEventLoopSize} sets the number of event loops the pool use (0 by default) +- a value of 0 configures the pool to use the event loop of the caller +- a positive value configures the pool load balance the creation of connection over a list of event loops determined by the value +- {@link io.vertx.core.http.PoolOptions options#setMaxWaitQueueSize} the maximum number of HTTP requests waiting until a connection is available, when the queue is full, the request is rejected + === Logging network client activity For debugging purposes, network activity can be logged. @@ -861,6 +899,23 @@ For debugging purposes, network activity can be logged. See the chapter on <> for a detailed explanation. +=== Advanced HTTP client creation + +You can pass options {@link io.vertx.core.Vertx#createHttpClient} methods to configure the HTTP client. + +Alternatively you can build a client with the builder {@link io.vertx.core.http.HttpClientBuilder API} : + +[source,$lang] +---- +{@link examples.HTTPExamples#exampleClientBuilder01} +---- + +In addition to {@link io.vertx.core.http.HttpClientOptions} and {@link io.vertx.core.http.PoolOptions}, you +can set + +- a connection event handler notified when the client <<_client_connections,connects>> to a server +- a redirection handler to implement an alternative HTTP <<_30x_redirection_handling,redirect>> behavior + === Making requests The http client is very flexible and there are various ways you can make requests with it. @@ -1020,7 +1075,7 @@ An {@link io.vertx.core.http.HttpClientRequest} instance is also a {@link io.ver You can pipe to it from any {@link io.vertx.core.streams.ReadStream} instance. -For, example, you could pipe a file on disk to a http request body as follows: +For, example, you could pipe a file on disk to an http request body as follows: [source,$lang] ---- @@ -1046,11 +1101,28 @@ no need to set the `Content-Length` of the request up-front. ==== Request timeouts -You can set a timeout for a specific http request using {@link io.vertx.core.http.RequestOptions#setTimeout(long)} or -{@link io.vertx.core.http.HttpClientRequest#setTimeout(long)}. +You can set an idle timeout to prevent your application from unresponsive servers using {@link io.vertx.core.http.RequestOptions#setIdleTimeout(long)} or {@link io.vertx.core.http.HttpClientRequest#idleTimeout(long)}. When the request does not return any data within the timeout period an exception will fail the result and the request will be reset. + +[source,$lang] +---- +{@link examples.HTTPExamples#clientIdleTimeout} +---- + +NOTE: the timeout starts when the {@link io.vertx.core.http.HttpClientRequest} is available, implying a connection was +obtained from the pool. + +You can set a connect timeout to prevent your application from unresponsive busy client connection pool. The +`Future` is failed when a connection is not obtained before the timeout delay. + +The connect timeout option is not related to the TCP {@link io.vertx.core.http.HttpClientOptions#setConnectTimeout(int)} option, when a request is made against a pooled HTTP client, the timeout applies to the duration to obtain a connection from the pool to serve the request, +the timeout might fire because the server does not respond in time or the pool is too busy to serve a request. -If the request does not return any data within the timeout period an exception will be passed to the exception handler -(if provided) and the request will be closed. +You can configure both timeout using {@link io.vertx.core.http.RequestOptions#setTimeout(long)} + +[source,$lang] +---- +{@link examples.HTTPExamples#clientTimeout} +---- ==== Writing HTTP/2 frames @@ -1093,6 +1165,14 @@ The request handler are notified of stream reset events with the {@link io.vertx {@link examples.HTTP2Examples#example12} ---- +=== HTTP/2 RST flood protection + +An HTTP/2 server is protected against RST flood DDOS attacks (https://github.com/netty/netty/security/advisories/GHSA-xpw8-rcwv-8f8p[CVE-2023-44487]): there is an upper bound to the number of `RST` +frames a server can receive in a time window. The default configuration sets the upper bound to `200` for a duration of +`30` seconds. + +You can use {@link io.vertx.core.http.HttpServerOptions#setHttp2RstFloodMaxRstFramePerWindow} and {@link io.vertx.core.http.HttpServerOptions#setHttp2RstFloodWindowDuration} to override these settings. + === Handling HTTP responses You receive an instance of {@link io.vertx.core.http.HttpClientResponse} into the handler that you specify in of @@ -1229,7 +1309,7 @@ Alternatively you can just parse the `Set-Cookie` headers yourself in the respon The client can be configured to follow HTTP redirections provided by the `Location` response header when the client receives: -* a `301`, `302`, `307` or `308` status code along with a HTTP GET or HEAD method +* a `301`, `302`, `307` or `308` status code along with an HTTP GET or HEAD method * a `303` status code, in addition the directed request perform an HTTP GET method Here's an example: @@ -1394,11 +1474,22 @@ If the body of the response was compressed via gzip it will include for example Content-Encoding: gzip -To enable compression set {@link io.vertx.core.http.HttpClientOptions#setTryUseCompression(boolean)} on the options +To enable compression set {@link io.vertx.core.http.HttpClientOptions#setDecompressionSupported(boolean)} on the options used when creating the client. By default compression is disabled. +=== Client side load balancing + +By default, when the client resolves a hostname to a list of several IP addresses, the client uses the first returned IP address. + +The http client can be configured to perform client side load balancing instead + +[source,$lang] +---- +{@link examples.HTTPExamples#httpClientSideLoadBalancing} +---- + === HTTP/1.x pooling and keep alive Http keep alive allows http connections to be used for more than one request. This can be a more efficient use of @@ -1413,7 +1504,7 @@ When keep alive is enabled. Vert.x will add a `Connection: Keep-Alive` header to When keep alive is disabled. Vert.x will add a `Connection: Close` header to each HTTP/1.1 request sent to signal that the connection will be closed after completion of the response. -The maximum number of connections to pool *for each server* is configured using {@link io.vertx.core.http.HttpClientOptions#setMaxPoolSize(int)} +The maximum number of connections to pool *for each server* is configured using {@link io.vertx.core.http.PoolOptions#setHttp1MaxSize(int)} When making a request with pooling enabled, Vert.x will create a new connection if there are less than the maximum number of connections already created for that server, otherwise it will add the request to a queue. @@ -1449,7 +1540,7 @@ fairness of the distribution of the client requests over the connections to the HTTP/2 advocates to use a single connection to a server, by default the http client uses a single connection for each server, all the streams to the same server are multiplexed over the same connection. -When the clients needs to use more than a single connection and use pooling, the {@link io.vertx.core.http.HttpClientOptions#setHttp2MaxPoolSize(int)} +When the clients needs to use more than a single connection and use pooling, the {@link io.vertx.core.http.PoolOptions#setHttp2MaxSize(int)} shall be used. When it is desirable to limit the number of multiplexed streams per connection and use a connection @@ -1507,7 +1598,7 @@ The {@link io.vertx.core.http.HttpClientRequest#connection()} method returns the {@link examples.HTTP2Examples#example18} ---- -A connection handler can be set on the client to be notified when a connection has been established happens: +A connection handler can be set on a client builder to be notified when a connection has been established happens: [source,$lang] ---- @@ -1621,7 +1712,7 @@ NOTE: this only applies to the HTTP/2 protocol Connection {@link io.vertx.core.http.HttpConnection#close} closes the connection: - it closes the socket for HTTP/1.x -- a shutdown with no delay for HTTP/2, the {@literal GOAWAY} frame will still be sent before the connection is closed. * +- a shutdown with no delay for HTTP/2, the {@literal GOAWAY} frame will still be sent before the connection is closed. The {@link io.vertx.core.http.HttpConnection#closeHandler} notifies when a connection is closed. @@ -1660,7 +1751,7 @@ You can assign a number of event loop a client will use independently of the cli When several HTTP servers listen on the same port, vert.x orchestrates the request handling using a round-robin strategy. -Let's take a verticle creating a HTTP server such as: +Let's take a verticle creating an HTTP server such as: .io.vertx.examples.http.sharing.HttpServerVerticle [source,$lang] @@ -1668,14 +1759,17 @@ Let's take a verticle creating a HTTP server such as: {@link examples.HTTPExamples#serversharing(io.vertx.core.Vertx)} ---- -This service is listening on the port 8080. So, when this verticle is instantiated multiple times as with: -`vertx run io.vertx.examples.http.sharing.HttpServerVerticle -instances 2`, what's happening ? If both -verticles would bind to the same port, you would receive a socket exception. Fortunately, vert.x is handling -this case for you. When you deploy another server on the same host and port as an existing server it doesn't -actually try and create a new server listening on the same host/port. It binds only once to the socket. When -receiving a request it calls the server handlers following a round robin strategy. +This service is listening on the port 8080. + +So, when this verticle is instantiated multiple times as with: `deploymentOptions.setInstances(2)`, what's happening ? +If both verticles bound to the same port, you would receive a socket exception. +Fortunately, vert.x is handling this case for you. +When you deploy another server on the same host and port as an existing server it doesn't actually try and create a new server listening on the same host/port. +It binds only once to the socket. +When receiving a request it calls the server handlers following a round-robin strategy. Let's now imagine a client such as: + [source,$lang] ---- {@link examples.HTTPExamples#serversharingclient(io.vertx.core.Vertx)} @@ -1795,18 +1889,24 @@ The {@link io.vertx.core.http.ServerWebSocket} instance enables you to retrieve ==== WebSockets on the client -The Vert.x {@link io.vertx.core.http.HttpClient} supports WebSockets. - -You can connect a WebSocket to a server using one of the {@link io.vertx.core.http.HttpClient#webSocket} operations and -providing a handler. +e Vert.x {@link io.vertx.core.http.WebSocketClient} supports WebSockets. -The handler will be called with an instance of {@link io.vertx.core.http.WebSocket} when the connection has been made: + You can connect a WebSocket to a server using one of the {@link io.vertx.core.http.WebSocketClient#connect} operations. + The returned future will be completed with an instance of {@link io.vertx.core.http.WebSocket} when the connection has been made: [source,$lang] ---- {@link examples.HTTPExamples#example54} ---- +en connecting from a non Vert.x thread, you can create a {@link io.vertx.core.http.ClientWebSocket}, configure its handlers and +then connect to the server: + + [source,$lang] + ---- + {@link examples.HTTPExamples#example54_bis} + ---- + By default, the client sets the `origin` header to the server host, e.g http://www.example.com. Some servers will refuse such request, you can configure the client to not set this header. @@ -1836,7 +1936,7 @@ If you wish to write a single WebSocket message to the WebSocket you can do this ---- If the WebSocket message is larger than the maximum WebSocket frame size as configured with -{@link io.vertx.core.http.HttpClientOptions#setMaxWebSocketFrameSize(int)} +{@link io.vertx.core.http.WebSocketClientOptions#setMaxFrameSize(int)} then Vert.x will split it into multiple WebSocket frames before sending it on the wire. ==== Writing frames to WebSockets @@ -1909,7 +2009,7 @@ The addresses of the handlers are given by {@link io.vertx.core.http.WebSocket#b === Using a proxy for HTTP/HTTPS connections -The http client supports accessing http/https URLs via a HTTP proxy (e.g. Squid) or _SOCKS4a_ or _SOCKS5_ proxy. +The http client supports accessing http/https URLs via an HTTP proxy (e.g. Squid) or _SOCKS4a_ or _SOCKS5_ proxy. The CONNECT protocol uses HTTP/1.x but can connect to HTTP/1.x and HTTP/2 servers. Connecting to h2c (unencrypted HTTP/2 servers) is likely not supported by http proxies since they will support diff --git a/src/main/asciidoc/index.adoc b/src/main/asciidoc/index.adoc index 317f360c805..b093c2a46a4 100644 --- a/src/main/asciidoc/index.adoc +++ b/src/main/asciidoc/index.adoc @@ -16,6 +16,7 @@ Vert.x core provides functionality for things like: * Datagram Sockets * DNS client * File system access +* Virtual threads * High availability * Native transports * Clustering @@ -34,11 +35,68 @@ Instead, we automatically generate an *idiomatic* equivalent of the core Java AP From now on we'll just use the word *core* to refer to Vert.x core. -include::override/dependencies.adoc[] +If you are using Maven or Gradle, add the following dependency to the _dependencies_ section of your +project descriptor to access the Vert.x Core API: + +* Maven (in your `pom.xml`): + +[source,xml,subs="+attributes"] +---- + + io.vertx + vertx-core + ${maven.version} + +---- + +* Gradle (in your `build.gradle` file): + +[source,groovy,subs="+attributes"] +---- +dependencies { + compile 'io.vertx:vertx-core:${maven.version}' +} +---- Let's discuss the different concepts and features in core. -include::override/in-the-beginning.adoc[] +== In the beginning there was Vert.x + +You can't do much in Vert.x-land unless you can communicate with a {@link io.vertx.core.Vertx} object! + +It's the control centre of Vert.x and is how you do pretty much everything, including creating clients and servers, +getting a reference to the event bus, setting timers, as well as many other things. + +So how do you get an instance? + +If you're embedding Vert.x then you simply create an instance as follows: + +[source,$lang] +---- +{@link examples.CoreExamples#example1} +---- + +NOTE: Most applications will only need a single Vert.x instance, but it's possible to create multiple Vert.x instances if you +require, for example, isolation between the event bus or different groups of servers and clients. + +=== Specifying options when creating a Vertx object + +When creating a Vert.x object you can also specify options if the defaults aren't right for you: + +[source,$lang] +---- +{@link examples.CoreExamples#example2} +---- + +The {@link io.vertx.core.VertxOptions} object has many settings and allows you to configure things like clustering, high availability, pool sizes and various other settings. + +=== Creating a clustered Vert.x object + +If you're creating a *clustered Vert.x* (See the section on the <> for more information on clustering the event bus), +then you will normally use the asynchronous variant to create the Vertx object. + +This is because it usually takes some time (maybe a few seconds) for the different Vert.x instances in a cluster to group together. +During that time, we don't want to block the calling thread, so we give the result to you asynchronously. == Are you fluent? @@ -223,7 +281,93 @@ You can think of a verticle as a bit like an actor in the http://en.wikipedia.or An application would typically be composed of many verticle instances running in the same Vert.x instance at the same time. The different verticle instances communicate with each other by sending messages on the <>. -include::override/verticles.adoc[] +=== Writing Verticles + +Verticle classes must implement the {@link io.vertx.core.Verticle} interface. + +They can implement it directly if you like but usually it's simpler to extend +the abstract class {@link io.vertx.core.AbstractVerticle}. + +Here's an example verticle: + +---- +public class MyVerticle extends AbstractVerticle { + + // Called when verticle is deployed + public void start() { + } + + // Optional - called when verticle is undeployed + public void stop() { + } + +} +---- + +Normally you would override the start method like in the example above. + +When Vert.x deploys the verticle it will call the start method, and when the method has completed the verticle will +be considered started. + +You can also optionally override the stop method. This will be called by Vert.x when the verticle is undeployed and when +the method has completed the verticle will be considered stopped. + +=== Asynchronous Verticle start and stop + +Sometimes you want to do something in your verticle start-up which takes some time and you don't want the verticle to +be considered deployed until that happens. For example you might want to start an HTTP server in the start method and +propagate the asynchronous result of the server `listen` method. + +You can't block waiting for the HTTP server to bind in your start method as that would break the <>. + +So how can you do this? + +The way to do it is to implement the *asynchronous* start method. This version of the method takes a Future as a parameter. +When the method returns the verticle will *not* be considered deployed. + +Some time later, after you've done everything you need to do (e.g. start the HTTP server), you can call complete +on the Future (or fail) to signal that you're done. + +Here's an example: + +---- +public class MyVerticle extends AbstractVerticle { + + private HttpServer server; + + @Override + public void start(Promise startPromise) { + server = vertx.createHttpServer().requestHandler(req -> { + req.response() + .putHeader("content-type", "text/plain") + .end("Hello from Vert.x!"); + }); + + // Now bind the server: + server.listen(8080) + .mapEmpty() + .onComplete(startPromise); + } +} +---- + +Similarly, there is an asynchronous version of the stop method too. You use this if you want to do some verticle +cleanup that takes some time. + +---- +public class MyVerticle extends AbstractVerticle { + + @Override + public void stop(Promise stopPromise) { + doSomethingThatTakesTime() + .mapEmpty() + .onComplete(stopPromise); + } +} +---- + +INFO: You don't need to manually stop the HTTP server started by a verticle, in the verticle's stop method. Vert.x +will automatically stop any running server when the verticle is undeployed. === Verticle Types @@ -258,7 +402,7 @@ Worker verticles are designed for calling blocking code, as they won't block any If you don't want to use a worker verticle to run blocking code, you can also run <> directly while on an event loop. -If you want to deploy a verticle as a worker verticle you do that with {@link io.vertx.core.DeploymentOptions#setWorker}. +If you want to deploy a verticle as a worker verticle you do that with {@link io.vertx.core.DeploymentOptions#setThreadingModel}. [source,$lang] ---- @@ -268,6 +412,21 @@ If you want to deploy a verticle as a worker verticle you do that with {@link io Worker verticle instances are never executed concurrently by Vert.x by more than one thread, but can executed by different threads at different times. +=== Virtual thread verticles + +A virtual thread verticle is just like a standard verticle but it's executed using virtual threads, rather than using an event loop. + +Virtual thread verticles are designed to use an async/await model with Vert.x futures. + +If you want to deploy a verticle as a <> you do that with {@link io.vertx.core.DeploymentOptions#setThreadingModel}. + +[source,$lang] +---- +{@link examples.CoreExamples#example7_2} +---- + +NOTE: this feature requires Java 21 + === Deploying verticles programmatically You can deploy a verticle using one of the {@link io.vertx.core.Vertx#deployVerticle} method, specifying a verticle @@ -368,65 +527,33 @@ want to deploy: This is useful for scaling easily across multiple cores. For example you might have a web-server verticle to deploy and multiple cores on your machine, so you want to deploy multiple instances to utilise all the cores. -include::override/verticle-configuration.adoc[] - -=== High Availability - -Verticles can be deployed with High Availability (HA) enabled. In that context, when a verticle is deployed on -a vert.x instance that dies abruptly, the verticle is redeployed on another vert.x instance from the cluster. +=== Passing configuration to a verticle -To run an verticle with the high availability enabled, just append the `-ha` switch: +Configuration in the form of JSON can be passed to a verticle at deployment time: -[source] +[source,$lang] ---- -vertx run my-verticle.js -ha +{@link examples.CoreExamples#example13} ---- -When enabling high availability, no need to add `-cluster`. - -More details about the high availability feature and configuration in the <> - section. - - -=== Running Verticles from the command line - -You can use Vert.x directly in your Maven or Gradle projects in the normal way by adding a dependency to the Vert.x -core library and hacking from there. - -However you can also run Vert.x verticles directly from the command line if you wish. - -To do this you need to download and install a Vert.x distribution, and add the `bin` directory of the installation -to your `PATH` environment variable. Also make sure you have a Java JDK on your `PATH`. - -Vert.x supports Java from 8 to 17. - -NOTE: The JDK is required to support on the fly compilation of Java code. - -You can now run verticles by using the `vertx run` command. Here are some examples: +This configuration is then available via the {@link io.vertx.core.Context} object or directly using the +{@link io.vertx.core.AbstractVerticle#config()} method. The configuration is returned as a JSON object so you +can retrieve data as follows: +[source,$lang] ---- -# Run a JavaScript verticle -vertx run my_verticle.js - -# Run a Ruby verticle -vertx run a_n_other_verticle.rb - -# Run a Groovy script verticle, clustered -vertx run FooVerticle.groovy -cluster +{@link examples.ConfigurableVerticleExamples#start()} ---- -You can even run Java source verticles without compiling them first! +=== Accessing environment variables in a Verticle + +Environment variables and system properties are accessible using the Java API: +[source,$lang] ---- -vertx run SomeJavaSourceFile.java +{@link examples.CoreExamples#systemAndEnvProperties()} ---- -Vert.x will compile the Java source file on the fly before running it. This is really useful for quickly -prototyping verticles and great for demos. No need to set-up a Maven or Gradle build first to get going! - -For full information on the various options available when executing `vertx` on the command line, -type `vertx` at the command line. - === Causing Vert.x to exit Threads maintained by Vert.x instances are not daemon threads so they will prevent the JVM from exiting. @@ -550,7 +677,7 @@ A different worker pool can be specified in deployment options: [[event_bus]] include::eventbus.adoc[] -include::override/json.adoc[] +include::json.adoc[] include::json-pointers.adoc[] @@ -568,6 +695,9 @@ include::datagrams.adoc[] include::dns.adoc[] +[[virtual_threads]] +include::virtualthreads.adoc[] + [[streams]] include::streams.adoc[] @@ -603,8 +733,8 @@ blocking APIs safely within a Vert.x application. As discussed before, you can't call blocking operations directly from an event loop, as that would prevent it from doing any other useful work. So how can you do this? -It's done by calling {@link io.vertx.core.Vertx#executeBlocking} specifying both the blocking code to execute and a -result handler to be called back asynchronous when the blocking code has been executed. +It's done by calling {@link io.vertx.core.Vertx#executeBlocking} with blocking code to execute, as return you get a +future completed with the result of the blocking code execution. [source,$lang] ---- @@ -621,7 +751,7 @@ which can interact with verticles using the event-bus or {@link io.vertx.core.Co By default, if executeBlocking is called several times from the same context (e.g. the same verticle instance) then the different executeBlocking are executed _serially_ (i.e. one after another). -If you don't care about ordering you can call {@link io.vertx.core.Vertx#executeBlocking(io.vertx.core.Handler,boolean)} +If you don't care about ordering you can call {@link io.vertx.core.Vertx#executeBlocking(java.util.concurrent.Callable,boolean)} specifying `false` as the argument to `ordered`. In this case any executeBlocking may be executed in parallel on the worker pool. @@ -660,334 +790,21 @@ Worker executors can be configured when created: NOTE: the configuration is set when the worker pool is created -== Metrics SPI - -By default Vert.x does not record any metrics. Instead it provides an SPI for others to implement which can be added -to the classpath. The metrics SPI is an advanced feature which allows implementers to capture events from Vert.x in -order to gather metrics. For more information on this, please consult the -{@link io.vertx.core.spi.metrics.VertxMetrics API Documentation}. +== Vert.x SPI -You can also specify a metrics factory programmatically if embedding Vert.x using -{@link io.vertx.core.metrics.MetricsOptions#setFactory(io.vertx.core.spi.VertxMetricsFactory)}. +A Vert.x instance has a few extension points knows as _SPI_ (Service Provider Interface). -== The 'vertx' command line +Such SPI are often loaded from the classpath using Java's `ServiceLoader` mechanism. -The `vertx` command is used to interact with Vert.x from the command line. It's main use is to run Vert.x verticles. -To do this you need to download and install a Vert.x distribution, and add the `bin` directory of the installation -to your `PATH` environment variable. Also make sure you have a Java JDK on your `PATH`. +=== Metrics and tracing SPI -Vert.x supports Java from 8 to 17. +By default, Vert.x does not record any metrics nor does any tracing. Instead, it provides an SPI for others to implement which can be added +to the classpath. The metrics SPI is a feature which allows implementers to capture events from Vert.x in order to +collect and report metrics, likewise the tracing SPI does the same for traces. -NOTE: The JDK is required to support on the fly compilation of Java code. +For more information on this, please consult https://vertx.io/docs/#monitoring -=== Run verticles - -You can run raw Vert.x verticles directly from the command line using `vertx run`. Here is a couple of examples of -the `run` _command_: - -[source] ----- -vertx run my-verticle.js (1) -vertx run my-verticle.groovy (2) -vertx run my-verticle.rb (3) - -vertx run io.vertx.example.MyVerticle (4) -vertx run io.vertx.example.MVerticle -cp my-verticle.jar (5) - -vertx run MyVerticle.java (6) ----- -1. Deploys a JavaScript verticle -2. Deploys a Groovy verticle -3. Deploys a Ruby verticle -4. Deploys an already compiled Java verticle. Classpath root is the current directory -5. Deploys a verticle packaged in a Jar, the jar need to be in the classpath -6. Compiles the Java source and deploys it - -As you can see in the case of Java, the name can either be the fully qualified class name of the verticle, or -you can specify the Java Source file directly and Vert.x compiles it for you. - -You can also prefix the verticle with the name of the language implementation to use. For example if the verticle is -a compiled Groovy class, you prefix it with `groovy:` so that Vert.x knows it's a Groovy class not a Java class. - -[source] ----- -vertx run groovy:io.vertx.example.MyGroovyVerticle ----- - -The `vertx run` command can take a few optional parameters, they are: - - * `-options ` - Provides the Vert.x options. - `options` is the name of a JSON file that represents the Vert.x options, or a JSON string. This is optional. - * `-conf ` - Provides some configuration to the verticle. - `config` is the name of a JSON file that represents the configuration for the verticle, or a JSON string. This is optional. - * `-cp ` - The path on which to search for the verticle and any other resources used by the verticle. This - defaults to `.` (current directory). If your verticle references other scripts, classes or other resources - (e.g. jar files) then make sure these are on this path. The path can contain multiple path entries separated by - `:` (colon) or `;` (semi-colon) depending on the operating system. Each path entry can be an absolute or relative - path to a directory containing scripts, or absolute or relative filenames for jar or zip files. An example path - might be `-cp classes:lib/otherscripts:jars/myjar.jar:jars/otherjar.jar`. Always use the path to reference any - resources that your verticle requires. Do **not** put them on the system classpath as this can cause isolation - issues between deployed verticles. -* `-instances ` - The number of instances of the verticle to instantiate. Each verticle instance is -strictly single threaded so to scale your application across available cores you might want to deploy more than -one instance. If omitted a single instance will be deployed. - * `-worker` - This option determines whether the verticle is a worker verticle or not. - * `-cluster` - This option determines whether the Vert.x instance will attempt to form a cluster with other Vert.x - instances on the network. Clustering Vert.x instances allows Vert.x to form a distributed event bus with - other nodes. Default is `false` (not clustered). - * `-cluster-port` - If the `cluster` option has also been specified then this determines which port will be bound for - cluster communication with other Vert.x instances. Default is `0` - which means '_choose a free random port_'. You - don't usually need to specify this parameter unless you really need to bind to a specific port. - * `-cluster-host` - If the `cluster` option has also been specified then this determines which host address will be - bound for cluster communication with other Vert.x instances. If not set, the clustered eventbus tries to bind to the - same host as the underlying cluster manager. As a last resort, an address will be picked among the available network - interfaces. - * `-cluster-public-port` - If the `cluster` option has also been specified then this determines which port will be advertised for - cluster communication with other Vert.x instances. Default is `-1`, which means same as `cluster-port`. - * `-cluster-public-host` - If the `cluster` option has also been specified then this determines which host address will be advertised for - cluster communication with other Vert.x instances. If not specified, Vert.x uses the value of `cluster-host`. - * `-ha` - if specified the verticle will be deployed as high availability (HA) deployment. See related section - for more details - * `-quorum` - used in conjunction with `-ha`. It specifies the minimum number of nodes in the cluster for any _HA - deploymentIDs_ to be active. Defaults to 0. - * `-hagroup` - used in conjunction with `-ha`. It specifies the HA group this node will join. There can be - multiple HA groups in a cluster. Nodes will only failover to other nodes in the same group. The default value is ` - +++__DEFAULT__+++` - -You can also set system properties using: `-Dkey=value`. - -Here are some more examples: - -Run a JavaScript verticle server.js with default settings -[source] ----- -vertx run server.js ----- - -Run 10 instances of a pre-compiled Java verticle specifying classpath -[source] ----- -vertx run com.acme.MyVerticle -cp "classes:lib/myjar.jar" -instances 10 ----- - -Run 10 instances of a Java verticle by source _file_ -[source] ----- -vertx run MyVerticle.java -instances 10 ----- - -Run 20 instances of a ruby worker verticle -[source] ----- -vertx run order_worker.rb -instances 20 -worker ----- - -Run two JavaScript verticles on the same machine and let them cluster together with each other and any other servers -on the network -[source] ----- -vertx run handler.js -cluster -vertx run sender.js -cluster ----- - -Run a Ruby verticle passing it some config -[source] ----- -vertx run my_verticle.rb -conf my_verticle.conf ----- -Where `my_verticle.conf` might contain something like: - -[source, json] ----- -{ - "name": "foo", - "num_widgets": 46 -} ----- - -The config will be available inside the verticle via the core API. - -When using the high-availability feature of vert.x you may want to create a _bare_ instance of vert.x. This -instance does not deploy any verticles when launched, but will receive a verticle if another node of the cluster -dies. To create a _bare_ instance, launch: - -[source] ----- -vertx bare ----- - -Depending on your cluster configuration, you may have to append the `cluster-host` and `cluster-port` parameters. - -=== Executing a Vert.x application packaged as a fat jar - -A _fat jar_ is an executable jar embedding its dependencies. This means you don't have to have Vert.x pre-installed -on the machine on which you execute the jar. Like any executable Java jar it can be executed with. - -[source] ----- -java -jar my-application-fat.jar ----- - -There is nothing really Vert.x specific about this, you could do this with any Java application - -You can either create your own main class and specify that in the manifest, but it's recommended that you write your -code as verticles and use the Vert.x {@link io.vertx.core.Launcher} class (`io.vertx.core.Launcher`) as your main -class. This is the same main class used when running Vert.x at the command line and therefore allows you to -specify command line arguments, such as `-instances` in order to scale your application more easily. - -To deploy your verticle in a _fatjar_ like this you must have a _manifest_ with: - -* `Main-Class` set to `io.vertx.core.Launcher` -* `Main-Verticle` specifying the main verticle (fully qualified class name or script file name) - -You can also provide the usual command line arguments that you would pass to `vertx run`: -[source] ----- -java -jar my-verticle-fat.jar -cluster -conf myconf.json -java -jar my-verticle-fat.jar -cluster -conf myconf.json -cp path/to/dir/conf/cluster_xml ----- - -NOTE: Please consult the Maven/Gradle simplest and Maven/Gradle verticle examples in the examples repository for -examples of building applications as fatjars. - -A fat jar executes the `run` command, by default. - -=== Displaying version of Vert.x -To display the vert.x version, just launch: - -[source] ----- -vertx version ----- - -=== Other commands - -The `vertx` command line and the `Launcher` also provide other _commands_ in addition to `run` and `version`: - -You can create a `bare` instance using: - -[source] ----- -vertx bare -# or -java -jar my-verticle-fat.jar bare ----- - -You can also start an application in background using: - -[source] ----- -java -jar my-verticle-fat.jar start --vertx-id=my-app-name ----- - -If `my-app-name` is not set, a random id will be generated, and printed on the command prompt. You can pass `run` -options to the `start` command: - -[source] ----- -java -jar my-verticle-fat.jar start —-vertx-id=my-app-name -cluster ----- - -Once launched in background, you can stop it with the `stop` command: - -[source] ----- -java -jar my-verticle-fat.jar stop my-app-name ----- - -You can also list the vert.x application launched in background using: - -[source] ----- -java -jar my-verticle-fat.jar list ----- - -The `start`, `stop` and `list` command are also available from the `vertx` tool. The start` command supports a couple of options: - - * `vertx-id` : the application id, uses a random UUID if not set - * `java-opts` : the Java Virtual Machine options, uses the `JAVA_OPTS` environment variable if not set. - * `redirect-output` : redirect the spawned process output and error streams to the parent process streams. - - If option values contain spaces, don't forget to wrap the value between `""` (double-quotes). - - As the `start` command spawns a new process, the java options passed to the JVM are not propagated, so you **must** - use `java-opts` to configure the JVM (`-X`, `-D`...). If you use the `CLASSPATH` environment variable, be sure it - contains all the required jars (vertx-core, your jars and all the dependencies). - -The set of commands is extensible, refer to the <> section. - -=== Live Redeploy - -When developing it may be convenient to automatically redeploy your application upon file changes. The `vertx` -command line tool and more generally the {@link io.vertx.core.Launcher} class offers this feature. Here are some -examples: - -[source] ----- -vertx run MyVerticle.groovy --redeploy="**/*.groovy" --launcher-class=io.vertx.core.Launcher -vertx run MyVerticle.groovy --redeploy="**/*.groovy,**/*.rb" --launcher-class=io.vertx.core.Launcher -java io.vertx.core.Launcher run org.acme.MyVerticle --redeploy="**/*.class" --launcher-class=io.vertx.core -.Launcher -cp ... ----- - -The redeployment process is implemented as follows. First your application is launched as a background application -(with the `start` command). On matching file changes, the process is stopped and the application is restarted. -This avoids leaks, as the process is restarted. - -To enable the live redeploy, pass the `--redeploy` option to the `run` command. The `--redeploy` indicates the -set of file to _watch_. This set can use Ant-style patterns (with `\**`, `*` and `?`). You can specify -several sets by separating them using a comma (`,`). Patterns are relative to the current working directory. - -Parameters passed to the `run` command are passed to the application. Java Virtual Machine options can be -configured using `--java-opts`. For instance, to pass the `conf` parameter or a system property, you need to -use: `--java-opts="-conf=my-conf.json -Dkey=value"` - -The `--launcher-class` option determine with with _main_ class the application is launcher. It's generally -{@link io.vertx.core.Launcher}, but you have use you own _main_. - -The redeploy feature can be used in your IDE: - -* Eclipse - create a _Run_ configuration, using the `io.vertx.core.Launcher` class a _main class_. In the _Program -arguments_ area (in the _Arguments_ tab), write `run your-verticle-fully-qualified-name --redeploy=\**/*.java ---launcher-class=io.vertx.core.Launcher`. You can also add other parameters. The redeployment works smoothly as -Eclipse incrementally compiles your files on save. -* IntelliJ - create a _Run_ configuration (_Application_), set the _Main class_ to `io.vertx.core.Launcher`. In -the Program arguments write: `run your-verticle-fully-qualified-name --redeploy=\**/*.class ---launcher-class=io.vertx.core.Launcher`. To trigger the redeployment, you need to _make_ the project or -the module explicitly (_Build_ menu -> _Make project_). - -To debug your application, create your run configuration as a remote application and configure the debugger -using `--java-opts`. However, don't forget to re-plug the debugger after every redeployment as a new process is -created every time. - -You can also hook your build process in the redeploy cycle: - -[source] ----- -java -jar target/my-fat-jar.jar --redeploy="**/*.java" --on-redeploy="mvn package" -java -jar build/libs/my-fat-jar.jar --redeploy="src/**/*.java" --on-redeploy='./gradlew shadowJar' ----- - -The "on-redeploy" option specifies a command invoked after the shutdown of the application and before the -restart. So you can hook your build tool if it updates some runtime artifacts. For instance, you can launch `gulp` -or `grunt` to update your resources. Don't forget that passing parameters to your application requires the -`--java-opts` param: - -[source] ----- -java -jar target/my-fat-jar.jar --redeploy="**/*.java" --on-redeploy="mvn package" --java-opts="-Dkey=val" -java -jar build/libs/my-fat-jar.jar --redeploy="src/**/*.java" --on-redeploy='./gradlew shadowJar' --java-opts="-Dkey=val" ----- - -The redeploy feature also supports the following settings: - -* `redeploy-scan-period` : the file system check period (in milliseconds), 250ms by default -* `redeploy-grace-period` : the amount of time (in milliseconds) to wait between 2 re-deployments, 1000ms by default -* `redeploy-termination-period` : the amount of time to wait after having stopped the application (before -launching user command). This is useful on Windows, where the process is not killed immediately. The time is given -in milliseconds. 0 ms by default. - -== Cluster Managers +=== Cluster Manager SPI In Vert.x a cluster manager is used for various functions including: @@ -1003,17 +820,30 @@ The default cluster manager used in the Vert.x distributions is one that uses ht can be easily replaced by a different implementation as Vert.x cluster managers are pluggable. A cluster manager must implement the interface {@link io.vertx.core.spi.cluster.ClusterManager}. Vert.x locates -cluster managers at run-time by using the Java -https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html[Service Loader] functionality to locate -instances of {@link io.vertx.core.spi.cluster.ClusterManager} on the classpath. +cluster managers at run-time by using the Java https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html[Service Loader] functionality to locate instances of {@link io.vertx.core.spi.cluster.ClusterManager} on the classpath. If you are using Vert.x at the command line and you want to use clustering you should make sure the `lib` directory of the Vert.x installation contains your cluster manager jar. If you are using Vert.x from a Maven or Gradle project just add the cluster manager jar as a dependency of your project. -You can also specify cluster managers programmatically if embedding Vert.x using -{@link io.vertx.core.VertxOptions#setClusterManager(io.vertx.core.spi.cluster.ClusterManager)}. +For more information on this, please consult https://vertx.io/docs/#clustering + +=== Programmatic SPI selection and configuration + +The {@link io.vertx.core.VertxBuilder} gives you more control over the SPI selection and configuration. + +[source,$lang] +---- +{@link examples.CoreExamples#vertxBuilder} +---- + +Clustered instances can also be created. + +[source,$lang] +---- +{@link examples.CoreExamples#clusteredVertxBuilder} +---- == Logging @@ -1025,7 +855,6 @@ The logging backend is selected as follows: . JDK logging when a `vertx-default-jul-logging.properties` file is in the classpath or, . a backend present in the classpath, in the following order of preference: .. SLF4J -.. Log4J .. Log4J2 Otherwise Vert.x defaults to JDK logging. @@ -1105,154 +934,142 @@ SEVERE: java.io.IOException: Connection reset by peer It means that the client is resetting the HTTP connection instead of closing it. This message also indicates that you may have not consumed the complete payload (the connection was cut before you were able to). -include::override/hostname-resolution.adoc[] +== Host name resolution -== High Availability and Fail-Over +Vert.x uses an an address resolver for resolving host name into IP addresses instead of +the JVM built-in blocking resolver. -Vert.x allows you to run your verticles with high availability (HA) support. In that case, when a vert.x -instance running a verticle dies abruptly, the verticle is migrated to another vertx instance. The vert.x -instances must be in the same cluster. +A host name resolves to an IP address using: -=== Automatic failover +- the _hosts_ file of the operating system +- otherwise DNS queries against a list of servers -When vert.x runs with _HA_ enabled, if a vert.x instance where a verticle runs fails or dies, the verticle is -redeployed automatically on another vert.x instance of the cluster. We call this _verticle fail-over_. +By default it will use the list of the system DNS server addresses from the environment, if that list cannot be +retrieved it will use Google's public DNS servers `"8.8.8.8"` and `"8.8.4.4"`. -To run vert.x with the _HA_ enabled, just add the `-ha` flag to the command line: +DNS servers can be also configured when creating a {@link io.vertx.core.Vertx} instance: -[source] +[source,$lang] ---- -vertx run my-verticle.js -ha +{@link examples.CoreExamples#configureDNSServers} ---- -Now for HA to work, you need more than one Vert.x instances in the cluster, so let's say you have another -Vert.x instance that you have already started, for example: +The default port of a DNS server is `53`, when a server uses a different port, this port can be set +using a colon delimiter: `192.168.0.2:40000`. -[source] ----- -vertx run my-other-verticle.js -ha ----- +NOTE: sometimes it can be desirable to use the JVM built-in resolver, the JVM system property +_-Dvertx.disableDnsResolver=true_ activates this behavior -If the Vert.x instance that is running `my-verticle.js` now dies (you can test this by killing the process -with `kill -9`), the Vert.x instance that is running `my-other-verticle.js` will automatic deploy `my-verticle -.js` so now that Vert.x instance is running both verticles. +=== Failover -NOTE: the migration is only possible if the second vert.x instance has access to the verticle file (here -`my-verticle.js`). +When a server does not reply in a timely manner, the resolver will try the next one from the list, the search +is limited by {@link io.vertx.core.dns.AddressResolverOptions#setMaxQueries(int)} (the default value is `4` queries). -IMPORTANT: Please note that cleanly closing a Vert.x instance will not cause failover to occur, e.g. `CTRL-C` -or `kill -SIGINT` +A DNS query is considered as failed when the resolver has not received a correct answer within +{@link io.vertx.core.dns.AddressResolverOptions#getQueryTimeout()} milliseconds (the default value is `5` seconds). -You can also start _bare_ Vert.x instances - i.e. instances that are not initially running any verticles, they -will also failover for nodes in the cluster. To start a bare instance you simply do: +=== Server list rotation -[source] ----- -vertx run -ha ----- +By default the dns server selection uses the first one, the remaining servers are used for failover. -When using the `-ha` switch you do not need to provide the `-cluster` switch, as a cluster is assumed if you -want HA. +You can configure {@link io.vertx.core.dns.AddressResolverOptions#setRotateServers(boolean)} to `true` to let +the resolver perform a round-robin selection instead. It spreads the query load among the servers and avoids +all lookup to hit the first server of the list. -NOTE: depending on your cluster configuration, you may need to customize the cluster manager configuration -(Hazelcast by default), and/or add the `cluster-host` and `cluster-port` parameters. +Failover still applies and will use the next server in the list. -=== HA groups +=== Hosts mapping -When running a Vert.x instance with HA you can also optional specify a _HA group_. A HA group denotes a -logical group of nodes in the cluster. Only nodes with the same HA group will failover onto one another. If -you don't specify a HA group the default group `+++__DEFAULT__+++` is used. +The _hosts_ file of the operating system is used to perform a hostname lookup for an ipaddress. -To specify an HA group you use the `-hagroup` switch when running the verticle, e.g. +An alternative _hosts_ file can be used instead: -[source] +[source,$lang] ---- -vertx run my-verticle.js -ha -hagroup my-group +{@link examples.CoreExamples#configureHosts} ---- -Let's look at an example: +=== Search domains -In a first terminal: +By default the resolver will use the system DNS search domains from the environment. Alternatively an explicit search domain +list can be provided: -[source] +[source,$lang] ---- -vertx run my-verticle.js -ha -hagroup g1 +{@link examples.CoreExamples#configureSearchDomains()} ---- -In a second terminal, let's run another verticle using the same group: +When a search domain list is used, the threshold for the number of dots is `1` or loaded from `/etc/resolv.conf` +on Linux, it can be configured to a specific value with {@link io.vertx.core.dns.AddressResolverOptions#setNdots(int)}. -[source] ----- -vertx run my-other-verticle.js -ha -hagroup g1 ----- +=== MacOS configuration -Finally, in a third terminal, launch another verticle using a different group: +MacOS has a specific native extension to get the name server configuration of the system based on https://opensource.apple.com/tarballs/mDNSResponder/[Apple's open source mDNSResponder]. When this extension is not present, +Netty logs the following warning. -[source] ---- -vertx run yet-another-verticle.js -ha -hagroup g2 +[main] WARN io.netty.resolver.dns.DnsServerAddressStreamProviders - Can not find io.netty.resolver.dns.macos.MacOSDnsServerAddressStreamProvider in the classpath, fallback to system defaults. This may result in incorrect DNS resolutions on MacOS. ---- -If we kill the instance in terminal 1, it will fail over to the instance in terminal 2, not the instance in -terminal 3 as that has a different group. - -If we kill the instance in terminal 3, it won't get failed over as there is no other vert.x instance in that -group. +This extension is not required as its absence does not prevent Vert.x to execute, yet is *recommended*. -=== Dealing with network partitions - Quorum +You can add it to your classpath to improve the integration and remove the warning. -The HA implementation also supports quorum. A quorum is the minimum number of votes that a distributed -transaction has to obtain in order to be allowed to perform an operation in a distributed system. - -When starting a Vert.x instance you can instruct it that it requires a `quorum` before any HA deployments will -be deployed. In this context, a quorum is a minimum number of nodes for a particular group in the cluster. -Typically you chose your quorum size to `Q = 1 + N/2` where `N` is the number of nodes in the group. If there -are less than `Q` nodes in the cluster the HA deployments will undeploy. They will redeploy again if/when a -quorum is re-attained. By doing this you can prevent against network partitions, a.k.a. _split brain_. - -There is more information on quorum http://en.wikipedia.org/wiki/Quorum_(distributed_computing)[here]. - -To run vert.x instances with a quorum you specify `-quorum` on the command line, e.g. - -In a first terminal: -[source] +.Intel-based Mac +[source,xml] ---- -vertx run my-verticle.js -ha -quorum 3 + + mac-intel + + + mac + x86_64 + + + + + io.netty + netty-resolver-dns-native-macos + osx-x86_64 + + + + ---- -At this point the Vert.x instance will start but not deploy the module (yet) because there is only one node -in the cluster, not 3. - -In a second terminal: -[source] +.M1/M2 Mac +[source,xml] ---- -vertx run my-other-verticle.js -ha -quorum 3 + + mac-silicon + + + mac + aarch64 + + + + + io.netty + netty-resolver-dns-native-macos + osx-aarch_64 + + + + ---- -At this point the Vert.x instance will start but not deploy the module (yet) because there are only two nodes -in the cluster, not 3. +== Native transports -In a third console, you can start another instance of vert.x: +Vert.x can run with http://netty.io/wiki/native-transports.html[native transports] (when available) on BSD (OSX) and Linux: -[source] +[source,$lang] ---- -vertx run yet-another-verticle.js -ha -quorum 3 +{@link examples.CoreExamples#configureNative()} ---- -Yay! - we have three nodes, that's a quorum. At this point the modules will automatically deploy on all -instances. - -If we now close or kill one of the nodes the modules will automatically undeploy on the other nodes, as there -is no longer a quorum. - -Quorum can also be used in conjunction with ha groups. In that case, quorum are resolved for each particular -group. - -== Native transports - -Vert.x can run with http://netty.io/wiki/native-transports.html[native transports] (when available) on BSD (OSX) and Linux: - -include::override/configuring-native.adoc[] +NOTE: preferring native transport will not prevent the application to execute (for example if a JAR is missing). +If your application requires native transport, you need to check {@link io.vertx.core.Vertx#isNativeTransportEnabled()}. === Native Linux Transport @@ -1382,105 +1199,6 @@ follow security best practice, especially if your service is public facing. For example you should always run them in a DMZ and with an user account that has limited rights in order to limit the extent of damage in case the service was compromised. -== Vert.x Command Line Interface API - -include::cli.adoc[] - -== The vert.x Launcher - -The vert.x {@link io.vertx.core.Launcher} is used in _fat jar_ as main class, and by the `vertx` command line -utility. It executes a set of _commands_ such as _run_, _bare_, _start_... - -=== Extending the vert.x Launcher - -You can extend the set of command by implementing your own {@link io.vertx.core.spi.launcher.Command} (in Java only): - -[source, java] ----- -@Name("my-command") -@Summary("A simple hello command.") -public class MyCommand extends DefaultCommand { - - private String name; - - @Option(longName = "name", required = true) - public void setName(String n) { - this.name = n; - } - - @Override - public void run() throws CLIException { - System.out.println("Hello " + name); - } -} ----- - -You also need an implementation of {@link io.vertx.core.spi.launcher.CommandFactory}: - -[source, java] ----- -public class HelloCommandFactory extends DefaultCommandFactory { - public HelloCommandFactory() { - super(HelloCommand.class); - } -} ----- - -Then, create the `src/main/resources/META-INF/services/io.vertx.core.spi.launcher.CommandFactory` and add a line -indicating the fully qualified name of the factory: - ----- -io.vertx.core.launcher.example.HelloCommandFactory ----- - -Builds the jar containing the command. Be sure to includes the SPI file -(`META-INF/services/io.vertx.core.spi.launcher.CommandFactory`). - -Then, place the jar containing the command into the classpath of your fat-jar (or include it inside) or in the `lib` -directory of your vert.x distribution, and you would be able to execute: - -[source] ----- -vertx hello vert.x -java -jar my-fat-jar.jar hello vert.x ----- - -=== Using the Launcher in fat jars - -To use the {@link io.vertx.core.Launcher} class in a _fat-jar_ just set the `Main-Class` of the _MANIFEST_ to -`io.vertx.core.Launcher`. In addition, set the `Main-Verticle` _MANIFEST_ entry to the name of your main verticle. - -By default, it executed the `run` command. However, you can configure the default command by setting the -`Main-Command` _MANIFEST_ entry. The default command is used if the _fat jar_ is launched without a command. - -=== Sub-classing the Launcher - -You can also create a sub-class of {@link io.vertx.core.Launcher} to start your application. The class has been -designed to be easily extensible. - -A {@link io.vertx.core.Launcher} sub-class can: - -* customize the vert.x configuration in {@link io.vertx.core.Launcher#beforeStartingVertx(io.vertx.core.VertxOptions)} -* retrieve the vert.x instance created by the "run" or "bare" command by -overriding {@link io.vertx.core.Launcher#afterStartingVertx(io.vertx.core.Vertx)} -* configure the default verticle and command with -{@link io.vertx.core.impl.launcher.VertxCommandLauncher#getMainVerticle()} and -{@link io.vertx.core.impl.launcher.VertxCommandLauncher#getDefaultCommand()} -* add / remove commands using {@link io.vertx.core.impl.launcher.VertxCommandLauncher#register(java.lang.Class)} -and {@link io.vertx.core.impl.launcher.VertxCommandLauncher#unregister(java.lang.String)} - -=== Launcher and exit code - -When you use the {@link io.vertx.core.Launcher} class as main class, it uses the following exit code: - -* `0` if the process ends smoothly, or if an uncaught error is thrown -* `1` for general purpose error -* `11` if Vert.x cannot be initialized -* `12` if a spawn process cannot be started, found or stopped. This error code is used by the `start` and -`stop` command -* `14` if the system configuration is not meeting the system requirement (shc as java not found) -* `15` if the main verticle cannot be deployed - == Configuring Vert.x cache When Vert.x needs to read a file from the classpath (embedded in a fat jar, in a jar form the classpath or a file @@ -1495,8 +1213,6 @@ application with: [source] ---- -vertx run my.Verticle -Dvertx.cacheDirBase=/tmp/vertx-cache -# or java -jar my-fat.jar vertx.cacheDirBase=/tmp/vertx-cache ---- @@ -1511,4 +1227,4 @@ directory and serves it from there. Do not use this setting in production, it ca Finally, you can disable completely the cache by using `-Dvertx.disableFileCPResolving=true`. This setting is not without consequences. Vert.x would be unable to read any files from the classpath (only from the file system). Be -very careful when using this settings. +very careful when using this setting. diff --git a/src/main/asciidoc/override/json.adoc b/src/main/asciidoc/json.adoc similarity index 88% rename from src/main/asciidoc/override/json.adoc rename to src/main/asciidoc/json.adoc index 8f3d263c5b2..535d1cad5b8 100644 --- a/src/main/asciidoc/override/json.adoc +++ b/src/main/asciidoc/json.adoc @@ -21,14 +21,14 @@ You can create a JSON object from a string JSON representation as follows: [source,java] ---- -{@link docoverride.json.Examples#example0_1} +{@link examples.JsonExamples#example0_1} ---- You can create a JSON object from a map as follows: [source,java] ---- -{@link docoverride.json.Examples#exampleCreateFromMap} +{@link examples.JsonExamples#exampleCreateFromMap} ---- ==== Putting entries into a JSON object @@ -39,7 +39,7 @@ The method invocations can be chained because of the fluent API: [source,java] ---- -{@link docoverride.json.Examples#example1} +{@link examples.JsonExamples#example1} ---- ==== Getting values from a JSON object @@ -48,7 +48,7 @@ You get values from a JSON object using the `getXXX` methods, for example: [source,java] ---- -{@link docoverride.json.Examples#example2} +{@link examples.JsonExamples#example2} ---- ==== Mapping between JSON objects and Java objects @@ -59,7 +59,7 @@ You can instantiate a Java object and populate its fields from a JSON object as [source,java] ---- -{@link docoverride.json.Examples#mapToPojo} +{@link examples.JsonExamples#mapToPojo} ---- Note that both of the above mapping directions use Jackson's `ObjectMapper#convertValue()` to perform the @@ -92,7 +92,7 @@ You can create a JSON array from a string JSON representation as follows: [source,java] ---- -{@link docoverride.json.Examples#example0_2} +{@link examples.JsonExamples#example0_2} ---- ==== Adding entries into a JSON array @@ -101,7 +101,7 @@ You add entries to a JSON array using the {@link io.vertx.core.json.JsonArray#ad [source,java] ---- -{@link docoverride.json.Examples#example3} +{@link examples.JsonExamples#example3} ---- ==== Getting values from a JSON array @@ -110,7 +110,7 @@ You get values from a JSON array using the `getXXX` methods, for example: [source,java] ---- -{@link docoverride.json.Examples#example4} +{@link examples.JsonExamples#example4} ---- ==== Encoding a JSON array to a String @@ -125,5 +125,5 @@ When you are unsure of the string validity then you should use instead `{@link i [source,java] ---- -{@link docoverride.json.Examples#example5} +{@link examples.JsonExamples#example5} ---- diff --git a/src/main/asciidoc/net.adoc b/src/main/asciidoc/net.adoc index c1d9a3d30bb..1b9105f6352 100644 --- a/src/main/asciidoc/net.adoc +++ b/src/main/asciidoc/net.adoc @@ -183,6 +183,16 @@ This handler will then be called when the close has fully completed. {@link examples.NetExamples#example9} ---- +=== Client socket shutdown + +When a client is closed with a grace period, each socket opened by the client will be notified with a shutdown event, to +let the opportunity to perform a protocol level close before the actual socket close. + +[source,$lang] +---- +{@link examples.NetExamples#shutdownHandler} +---- + === Automatic clean-up in verticles If you're creating TCP servers and clients from inside verticles, those servers and clients will be automatically closed @@ -204,14 +214,6 @@ You can instantiate more instances programmatically in your code: {@link examples.NetExamples#example11} ---- -or, you can simply deploy more instances of your server verticle by using the `-instances` option -on the command line: - -[source] ----- -> vertx run com.mycompany.MyVerticle -instances 10 ----- - Once you do this you will find the echo server works functionally identically to before, but all your cores on your server can be utilised and more work can be handled. @@ -355,9 +357,27 @@ TCP server (Net/Http) can be configured with traffic shaping options to enable b bandwidth can be limited through {@link io.vertx.core.net.TrafficShapingOptions}. For NetServer, traffic shaping options can be set through {@link io.vertx.core.net.NetServerOptions} and for HttpServer it can be set through {@link io.vertx.core.http.HttpServerOptions}. +[source,$lang] +---- {@link examples.NetExamples#configureTrafficShapingForNetServer} +---- +[source,$lang] +---- {@link examples.NetExamples#configureTrafficShapingForHttpServer} +---- + +These traffic shaping options can also be dynamically updated after server start. + +[source,$lang] +---- +{@link examples.NetExamples#dynamicallyUpdateTrafficShapingForNetServer} +---- + +[source,$lang] +---- +{@link examples.NetExamples#dynamicallyUpdateTrafficShapingForHttpServer} +---- [[ssl]] === Configuring servers and clients to work with SSL/TLS @@ -377,7 +397,7 @@ By default it is disabled. ==== Specifying key/certificate for the server -SSL/TLS servers usually provide certificates to clients in order verify their identity to clients. +SSL/TLS servers usually provide certificates to clients in order to verify their identity to clients. Certificates/keys can be configured for servers in several ways: @@ -640,7 +660,11 @@ implement certificate rotation). {@link examples.NetExamples#updateSSLOptions} ---- -When the update succeeds the new SSL configuration is used, otherwise the previous configuration is kept. +When the update succeeds the new SSL configuration is used, otherwise the previous configuration is preserved. + +NOTE: The options object is compared (using `equals`) against the existing options to prevent an update when the objects +are equals since loading options can be costly. When object are equals, you can use the `force` parameter to force +the update. ==== Self-signed certificates for testing and development purposes @@ -809,13 +833,13 @@ Java TLS supports ALPN (Java 8 with the most recent versions). OpenSSL also supports (native) ALPN. -OpenSSL requires to configure {@link io.vertx.core.net.TCPSSLOptions#setOpenSslEngineOptions(OpenSSLEngineOptions)} +OpenSSL requires to configure {@link io.vertx.core.net.TCPSSLOptions#setSslEngineOptions(SSLEngineOptions)} and use http://netty.io/wiki/forked-tomcat-native.html[netty-tcnative] jar on the classpath. Using tcnative may require OpenSSL to be installed on your OS depending on the tcnative implementation. === Using a proxy for client connections -The {@link io.vertx.core.net.NetClient} supports either a HTTP/1.x _CONNECT_, _SOCKS4a_ or _SOCKS5_ proxy. +The {@link io.vertx.core.net.NetClient} supports either an HTTP/1.x _CONNECT_, _SOCKS4a_ or _SOCKS5_ proxy. The proxy can be configured in the {@link io.vertx.core.net.NetClientOptions} by setting a {@link io.vertx.core.net.ProxyOptions} object containing proxy type, hostname, port and optionally username and password. diff --git a/src/main/asciidoc/override/buffer_from_bytes.adoc b/src/main/asciidoc/override/buffer_from_bytes.adoc deleted file mode 100644 index 3f013f5da54..00000000000 --- a/src/main/asciidoc/override/buffer_from_bytes.adoc +++ /dev/null @@ -1,6 +0,0 @@ -Create a buffer from a byte[] - -[source,java] ----- -{@link docoverride.buffer.Examples#example4} ----- diff --git a/src/main/asciidoc/override/configuring-eventbus.adoc b/src/main/asciidoc/override/configuring-eventbus.adoc deleted file mode 100644 index 64a7d15fe31..00000000000 --- a/src/main/asciidoc/override/configuring-eventbus.adoc +++ /dev/null @@ -1,24 +0,0 @@ -The event bus can be configured. It is particularly useful when the event bus is clustered. -Under the hood the event bus uses TCP connections to send and receive messages, so the {@link io.vertx.core.eventbus.EventBusOptions} let you configure all aspects of these TCP connections. -As the event bus acts as a server and client, the configuration is close to {@link io.vertx.core.net.NetClientOptions} and {@link io.vertx.core.net.NetServerOptions}. - -[source,$lang] ----- -{@link examples.EventBusExamples#example13} ----- - -The previous snippet depicts how you can use SSL connections for the event bus, instead of plain TCP connections. - -**WARNING**: to enforce the security in clustered mode, you **must** configure the cluster manager to use encryption or enforce security. -Refer to the documentation of the cluster manager for further details. - -The event bus configuration needs to be consistent in all the cluster nodes. - -The {@link io.vertx.core.eventbus.EventBusOptions} also lets you specify whether or not the event bus is clustered, the port and host. - -When used in containers, you can also configure the public host and port: - -[source,$lang] ----- -{@link examples.EventBusExamples#example14} ----- diff --git a/src/main/asciidoc/override/configuring-native.adoc b/src/main/asciidoc/override/configuring-native.adoc deleted file mode 100644 index aabbb4e31e8..00000000000 --- a/src/main/asciidoc/override/configuring-native.adoc +++ /dev/null @@ -1,7 +0,0 @@ -[source,$lang] ----- -{@link examples.CoreExamples#configureNative()} ----- - -NOTE: preferring native transport will not prevent the application to execute (for example if a JAR is missing). -If your application requires native transport, you need to check {@link io.vertx.core.Vertx#isNativeTransportEnabled()}. diff --git a/src/main/asciidoc/override/dependencies.adoc b/src/main/asciidoc/override/dependencies.adoc deleted file mode 100644 index 554efdcfc4b..00000000000 --- a/src/main/asciidoc/override/dependencies.adoc +++ /dev/null @@ -1,22 +0,0 @@ -If you are using Maven or Gradle, add the following dependency to the _dependencies_ section of your -project descriptor to access the Vert.x Core API: - -* Maven (in your `pom.xml`): - -[source,xml,subs="+attributes"] ----- - - io.vertx - vertx-core - ${maven.version} - ----- - -* Gradle (in your `build.gradle` file): - -[source,groovy,subs="+attributes"] ----- -dependencies { - compile 'io.vertx:vertx-core:${maven.version}' -} ----- diff --git a/src/main/asciidoc/override/dns.adoc b/src/main/asciidoc/override/dns.adoc deleted file mode 100644 index b92b15a9773..00000000000 --- a/src/main/asciidoc/override/dns.adoc +++ /dev/null @@ -1,32 +0,0 @@ -=== Error handling - -As you saw in previous sections the DnsClient allows you to pass in a Handler which will be notified with an -AsyncResult once the query was complete. In case of an error it will be notified with a DnsException which will -hole a {@link io.vertx.core.dns.DnsResponseCode} that indicate why the resolution failed. This DnsResponseCode -can be used to inspect the cause in more detail. - -Possible DnsResponseCodes are: - -- {@link io.vertx.core.dns.DnsResponseCode#NOERROR} No record was found for a given query -- {@link io.vertx.core.dns.DnsResponseCode#FORMERROR} Format error -- {@link io.vertx.core.dns.DnsResponseCode#SERVFAIL} Server failure -- {@link io.vertx.core.dns.DnsResponseCode#NXDOMAIN} Name error -- {@link io.vertx.core.dns.DnsResponseCode#NOTIMPL} Not implemented by DNS Server -- {@link io.vertx.core.dns.DnsResponseCode#REFUSED} DNS Server refused the query -- {@link io.vertx.core.dns.DnsResponseCode#YXDOMAIN} Domain name should not exist -- {@link io.vertx.core.dns.DnsResponseCode#YXRRSET} Resource record should not exist -- {@link io.vertx.core.dns.DnsResponseCode#NXRRSET} RRSET does not exist -- {@link io.vertx.core.dns.DnsResponseCode#NOTZONE} Name not in zone -- {@link io.vertx.core.dns.DnsResponseCode#BADVERS} Bad extension mechanism for version -- {@link io.vertx.core.dns.DnsResponseCode#BADSIG} Bad signature -- {@link io.vertx.core.dns.DnsResponseCode#BADKEY} Bad key -- {@link io.vertx.core.dns.DnsResponseCode#BADTIME} Bad timestamp - -All of those errors are "generated" by the DNS Server itself. - -You can obtain the DnsResponseCode from the DnsException like: - -[source,java] ----- -{@link docoverride.dns.Examples#example16} ----- diff --git a/src/main/asciidoc/override/eventbus.adoc b/src/main/asciidoc/override/eventbus.adoc deleted file mode 100644 index c71819b873a..00000000000 --- a/src/main/asciidoc/override/eventbus.adoc +++ /dev/null @@ -1,41 +0,0 @@ -==== Message Codecs - -You can send any object you like across the event bus if you define and register a {@link io.vertx.core.eventbus.MessageCodec message codec} for it. - -Message codecs have a name and you specify that name in the {@link io.vertx.core.eventbus.DeliveryOptions} -when sending or publishing the message: - -[source,java] ----- -{@link docoverride.eventbus.Examples#example10} ----- - -If you always want the same codec to be used for a particular type then you can register a default codec for it, then -you don't have to specify the codec on each send in the delivery options: - -[source,java] ----- -{@link docoverride.eventbus.Examples#example11} ----- - -You unregister a message codec with {@link io.vertx.core.eventbus.EventBus#unregisterCodec}. - -Message codecs don't always have to encode and decode as the same type. For example you can write a codec that -allows a MyPOJO class to be sent, but when that message is sent to a handler it arrives as a MyOtherPOJO class. - -Vert.x has built-in codecs for certain data types: - -- basic types (string, byte array, byte, int, long, double, boolean, short, char), or -- some Vert.x data types (buffers, JSON array, JSON objects), or -- types implementing the {@link io.vertx.core.shareddata.ClusterSerializable} interface, or -- types implementing the `java.io.Serializable` interface. - -[IMPORTANT] -==== -In clustered mode, {@link io.vertx.core.shareddata.ClusterSerializable} and `java.io.Serializable` objects are rejected by default, for security reasons. - -You can define which classes are allowed for encoding and decoding by providing functions which inspect the name of the class: - -- {@link io.vertx.core.eventbus.EventBus#clusterSerializableChecker EventBus.clusterSerializableChecker()}, and -- {@link io.vertx.core.eventbus.EventBus#serializableChecker EventBus.serializableChecker()}. -==== diff --git a/src/main/asciidoc/override/eventbus_headers.adoc b/src/main/asciidoc/override/eventbus_headers.adoc deleted file mode 100644 index ec01fe30783..00000000000 --- a/src/main/asciidoc/override/eventbus_headers.adoc +++ /dev/null @@ -1,9 +0,0 @@ -==== Setting headers on messages - -Messages sent over the event bus can also contain headers. This can be specified by providing a -{@link io.vertx.core.eventbus.DeliveryOptions} when sending or publishing: - -[source,$lang] ----- -{@link docoverride.eventbus.Examples#headers(io.vertx.core.eventbus.EventBus)} ----- diff --git a/src/main/asciidoc/override/hostname-resolution.adoc b/src/main/asciidoc/override/hostname-resolution.adoc deleted file mode 100644 index e5a53ca2f81..00000000000 --- a/src/main/asciidoc/override/hostname-resolution.adoc +++ /dev/null @@ -1,125 +0,0 @@ -== Host name resolution - -Vert.x uses an an address resolver for resolving host name into IP addresses instead of -the JVM built-in blocking resolver. - -An host name resolves to an IP address using: - -- the _hosts_ file of the operating system -- otherwise DNS queries against a list of servers - -By default it will use the list of the system DNS server addresses from the environment, if that list cannot be -retrieved it will use Google's public DNS servers `"8.8.8.8"` and `"8.8.4.4"`. - -DNS servers can be also configured when creating a {@link io.vertx.core.Vertx} instance: - -[source,$lang] ----- -{@link examples.CoreExamples#configureDNSServers} ----- - -The default port of a DNS server is `53`, when a server uses a different port, this port can be set -using a colon delimiter: `192.168.0.2:40000`. - -NOTE: sometimes it can be desirable to use the JVM built-in resolver, the JVM system property -_-Dvertx.disableDnsResolver=true_ activates this behavior - -=== Failover - -When a server does not reply in a timely manner, the resolver will try the next one from the list, the search -is limited by {@link io.vertx.core.dns.AddressResolverOptions#setMaxQueries(int)} (the default value is `4` queries). - -A DNS query is considered as failed when the resolver has not received a correct answer within -{@link io.vertx.core.dns.AddressResolverOptions#getQueryTimeout()} milliseconds (the default value is `5` seconds). - -=== Server list rotation - -By default the dns server selection uses the first one, the remaining servers are used for failover. - -You can configure {@link io.vertx.core.dns.AddressResolverOptions#setRotateServers(boolean)} to `true` to let -the resolver perform a round-robin selection instead. It spreads the query load among the servers and avoids -all lookup to hit the first server of the list. - -Failover still applies and will use the next server in the list. - -=== Hosts mapping - -The _hosts_ file of the operating system is used to perform an hostname lookup for an ipaddress. - -An alternative _hosts_ file can be used instead: - -[source,$lang] ----- -{@link examples.CoreExamples#configureHosts} ----- - -=== Search domains - -By default the resolver will use the system DNS search domains from the environment. Alternatively an explicit search domain -list can be provided: - -[source,$lang] ----- -{@link examples.CoreExamples#configureSearchDomains()} ----- - -When a search domain list is used, the threshold for the number of dots is `1` or loaded from `/etc/resolv.conf` -on Linux, it can be configured to a specific value with {@link io.vertx.core.dns.AddressResolverOptions#setNdots(int)}. - -=== MacOS configuration - -MacOS has a specific native extension to get the name server configuration of the system based on -Apple's open source mDNSResponder. When this extension is not present, -Netty logs the following warning. - ----- -[main] WARN io.netty.resolver.dns.DnsServerAddressStreamProviders - Can not find io.netty.resolver.dns.macos.MacOSDnsServerAddressStreamProvider in the classpath, fallback to system defaults. This may result in incorrect DNS resolutions on MacOS. ----- - -This extension is not required as its absence does not prevent Vert.x to execute, yet is *recommended*. - -You can use add it to your classpath to improve the integration and remove the warning. - -.Intel-based Mac -[source,xml] ----- - - mac-intel - - - mac - x86_64 - - - - - io.netty - netty-resolver-dns-native-macos - osx-x86_64 - - - - ----- - -.M1/M2 Mac -[source,xml] ----- - - mac-silicon - - - mac - aarch64 - - - - - io.netty - netty-resolver-dns-native-macos - osx-aarch_64 - - - - ----- diff --git a/src/main/asciidoc/override/in-the-beginning.adoc b/src/main/asciidoc/override/in-the-beginning.adoc deleted file mode 100644 index e4f50f943dc..00000000000 --- a/src/main/asciidoc/override/in-the-beginning.adoc +++ /dev/null @@ -1,37 +0,0 @@ -== In the beginning there was Vert.x - -You can't do much in Vert.x-land unless you can communicate with a {@link io.vertx.core.Vertx} object! - -It's the control centre of Vert.x and is how you do pretty much everything, including creating clients and servers, -getting a reference to the event bus, setting timers, as well as many other things. - -So how do you get an instance? - -If you're embedding Vert.x then you simply create an instance as follows: - -[source,$lang] ----- -{@link examples.CoreExamples#example1} ----- - -NOTE: Most applications will only need a single Vert.x instance, but it's possible to create multiple Vert.x instances if you -require, for example, isolation between the event bus or different groups of servers and clients. - -=== Specifying options when creating a Vertx object - -When creating a Vert.x object you can also specify options if the defaults aren't right for you: - -[source,$lang] ----- -{@link examples.CoreExamples#example2} ----- - -The {@link io.vertx.core.VertxOptions} object has many settings and allows you to configure things like clustering, high availability, pool sizes and various other settings. - -=== Creating a clustered Vert.x object - -If you're creating a *clustered Vert.x* (See the section on the <> for more information on clustering the event bus), -then you will normally use the asynchronous variant to create the Vertx object. - -This is because it usually takes some time (maybe a few seconds) for the different Vert.x instances in a cluster to group together. -During that time, we don't want to block the calling thread, so we give the result to you asynchronously. diff --git a/src/main/asciidoc/override/verticle-configuration.adoc b/src/main/asciidoc/override/verticle-configuration.adoc deleted file mode 100644 index 99a52bf7ff9..00000000000 --- a/src/main/asciidoc/override/verticle-configuration.adoc +++ /dev/null @@ -1,26 +0,0 @@ -=== Passing configuration to a verticle - -Configuration in the form of JSON can be passed to a verticle at deployment time: - -[source,$lang] ----- -{@link examples.CoreExamples#example13} ----- - -This configuration is then available via the {@link io.vertx.core.Context} object or directly using the -{@link io.vertx.core.AbstractVerticle#config()} method. The configuration is returned as a JSON object so you -can retrieve data as follows: - -[source,$lang] ----- -{@link examples.ConfigurableVerticleExamples#start()} ----- - -=== Accessing environment variables in a Verticle - -Environment variables and system properties are accessible using the Java API: - -[source,$lang] ----- -{@link examples.CoreExamples#systemAndEnvProperties()} ----- diff --git a/src/main/asciidoc/override/verticles.adoc b/src/main/asciidoc/override/verticles.adoc deleted file mode 100644 index 53faeb1ca51..00000000000 --- a/src/main/asciidoc/override/verticles.adoc +++ /dev/null @@ -1,87 +0,0 @@ -=== Writing Verticles - -Verticle classes must implement the {@link io.vertx.core.Verticle} interface. - -They can implement it directly if you like but usually it's simpler to extend -the abstract class {@link io.vertx.core.AbstractVerticle}. - -Here's an example verticle: - ----- -public class MyVerticle extends AbstractVerticle { - - // Called when verticle is deployed - public void start() { - } - - // Optional - called when verticle is undeployed - public void stop() { - } - -} ----- - -Normally you would override the start method like in the example above. - -When Vert.x deploys the verticle it will call the start method, and when the method has completed the verticle will -be considered started. - -You can also optionally override the stop method. This will be called by Vert.x when the verticle is undeployed and when -the method has completed the verticle will be considered stopped. - -=== Asynchronous Verticle start and stop - -Sometimes you want to do something in your verticle start-up which takes some time and you don't want the verticle to -be considered deployed until that happens. For example you might want to start an HTTP server in the start method and -propagate the asynchronous result of the server `listen` method. - -You can't block waiting for the HTTP server to bind in your start method as that would break the <>. - -So how can you do this? - -The way to do it is to implement the *asynchronous* start method. This version of the method takes a Future as a parameter. -When the method returns the verticle will *not* be considered deployed. - -Some time later, after you've done everything you need to do (e.g. start the HTTP server), you can call complete -on the Future (or fail) to signal that you're done. - -Here's an example: - ----- -public class MyVerticle extends AbstractVerticle { - - private HttpServer server; - - @Override - public void start(Promise startPromise) { - server = vertx.createHttpServer().requestHandler(req -> { - req.response() - .putHeader("content-type", "text/plain") - .end("Hello from Vert.x!"); - }); - - // Now bind the server: - server.listen(8080) - .mapEmpty() - .onComplete(startPromise); - } -} ----- - -Similarly, there is an asynchronous version of the stop method too. You use this if you want to do some verticle -cleanup that takes some time. - ----- -public class MyVerticle extends AbstractVerticle { - - @Override - public void stop(Promise stopPromise) { - doSomethingThatTakesTime() - .mapEmpty() - .onComplete(stopPromise); - } -} ----- - -INFO: You don't need to manually stop the HTTP server started by a verticle, in the verticle's stop method. Vert.x -will automatically stop any running server when the verticle is undeployed. diff --git a/src/main/asciidoc/shareddata.adoc b/src/main/asciidoc/shareddata.adoc index bc2b1cc4eda..e2fcbd5d69e 100644 --- a/src/main/asciidoc/shareddata.adoc +++ b/src/main/asciidoc/shareddata.adoc @@ -143,6 +143,16 @@ If your application doesn't need the lock to be shared with every other node, yo {@link examples.SharedDataExamples#localLock} ---- +Sometimes, you use the lock API to retrieve an asynchronous result and apply the acquire/release pattern around the asynchronous call. Vert.x provides a simplified lock API to simplify this pattern. + +[source,$lang] +---- +{@link examples.SharedDataExamples#withLock} +---- + +The lock is acquired before calling the supplier and released when the future returned by the supplier +completes. + === Asynchronous counters It's often useful to maintain an atomic counter locally or across the different nodes of your application. diff --git a/src/main/asciidoc/streams.adoc b/src/main/asciidoc/streams.adoc index 6df8fc8c379..94aac890d71 100644 --- a/src/main/asciidoc/streams.adoc +++ b/src/main/asciidoc/streams.adoc @@ -119,7 +119,7 @@ Let's now look at the methods on `ReadStream` and `WriteStream` in more detail: `ReadStream` is implemented by {@link io.vertx.core.http.HttpClientResponse}, {@link io.vertx.core.datagram.DatagramSocket}, {@link io.vertx.core.http.HttpClientRequest}, {@link io.vertx.core.http.HttpServerFileUpload}, {@link io.vertx.core.http.HttpServerRequest}, {@link io.vertx.core.eventbus.MessageConsumer}, -{@link io.vertx.core.net.NetSocket}, {@link io.vertx.core.http.WebSocket}, {@link io.vertx.core.file.AsyncFile}. +{@link io.vertx.core.net.NetSocket}, {@link io.vertx.core.http.WebSocket} and {@link io.vertx.core.file.AsyncFile}. - {@link io.vertx.core.streams.ReadStream#handler}: set a handler which will receive items from the ReadStream. diff --git a/src/main/asciidoc/virtualthreads.adoc b/src/main/asciidoc/virtualthreads.adoc new file mode 100644 index 00000000000..a5a07137ed7 --- /dev/null +++ b/src/main/asciidoc/virtualthreads.adoc @@ -0,0 +1,100 @@ +== Vert.x Virtual Threads + +Use virtual threads to write Vert.x code that looks like it is synchronous. + +You still write the traditional Vert.x code processing events, but you have the opportunity to write synchronous code for complex workflows and use thread locals in such workflows. + +=== Introduction + +The non-blocking nature of Vert.x leads to asynchronous APIs. +Asynchronous APIs can take various forms including callback style, promises and reactive extensions. + +In some cases, programming using asynchronous APIs can be more challenging than using a direct synchronous style, in particular if you have several operations that you want to do sequentially. +Also, error propagation is often more complex when using asynchronous APIs. + +Virtual thread support allows you to work with asynchronous APIs, but using a direct synchronous style that you're already familiar with. + +It does this by using Java 21 https://openjdk.org/jeps/444[virtual threads]. Virtual threads are very lightweight threads that do not correspond to underlying kernel threads. When they are blocked they do not block a kernel thread. + +=== Using virtual threads + +You can deploy virtual thread verticles. + +A virtual thread verticle is capable of awaiting Vert.x futures and gets the result synchronously. + +When the verticle *awaits* a result, the verticle can still process events like an event-loop verticle. + +[source,java] +---- +{@link examples.VirtualThreadExamples#gettingStarted} +---- + +NOTE: Using virtual threads requires Java 21 or above. + +==== Blocking within a virtual thread verticle + +You can use {@link io.vertx.core.Future#await} to suspend the current virtual thread until the awaited result is available. + +The virtual thread is effectively blocked, but the application can still process events. + +When a virtual thread awaits for a result and the verticle needs to process a task, a new virtual thread is created to handle this task. + +When the result is available, the virtual thread execution is resumed and scheduled after the current task is suspended or finished. + +IMPORTANT: Like any verticle at most one task is executed at the same time. + +You can await on a Vert.x `Future` + +[source,java] +---- +{@link examples.VirtualThreadExamples#awaitingFutures1} +---- + +or on a JDK `CompletionStage` + +[source,java] +---- +{@link examples.VirtualThreadExamples#awaitingFutures2} +---- + +==== Field visibility + +A virtual thread verticle can interact safely with fields before an `await` call. However, if you are reading a field before an `await` call and reusing the value after the call, you should keep in mind that this value might have changed. + +[source,java] +---- +{@link examples.VirtualThreadExamples#fieldVisibility1} +---- + +You should read/write fields before calling `await` to avoid this. + +[source,java] +---- +{@link examples.VirtualThreadExamples#fieldVisibility2} +---- + +NOTE: this is the same behavior with an event-loop verticle + +==== Awaiting multiple futures + +When you need to await multiple futures, you can use Vert.x {@link io.vertx.core.CompositeFuture}: + +[source,java] +---- +{@link examples.VirtualThreadExamples#awaitingMultipleFutures} +---- + +==== Blocking without await + +When your application blocks without using `await`, e.g. using `ReentrantLock#lock`, the Vert.x scheduler is not aware of it and cannot schedule events on the verticle: it behaves like a worker verticle, yet using virtual threads. + +This use case is not encouraged yet not forbidden, however the verticle should be deployed with several instances to deliver the desired concurrency, like a worker verticle. + +==== Thread local support + +Thread locals are only reliable within the execution of a context task. + +[source,java] +---- +{@link examples.VirtualThreadExamples#threadLocalSupport1} +---- diff --git a/src/main/generated/io/vertx/core/DeploymentOptionsConverter.java b/src/main/generated/io/vertx/core/DeploymentOptionsConverter.java index 092b745f2c0..1605bbce4ba 100644 --- a/src/main/generated/io/vertx/core/DeploymentOptionsConverter.java +++ b/src/main/generated/io/vertx/core/DeploymentOptionsConverter.java @@ -25,6 +25,11 @@ static void fromJson(Iterable> json, Deploym obj.setConfig(((JsonObject)member.getValue()).copy()); } break; + case "threadingModel": + if (member.getValue() instanceof String) { + obj.setThreadingModel(io.vertx.core.ThreadingModel.valueOf((String)member.getValue())); + } + break; case "ha": if (member.getValue() instanceof Boolean) { obj.setHa((Boolean)member.getValue()); @@ -35,21 +40,6 @@ static void fromJson(Iterable> json, Deploym obj.setInstances(((Number)member.getValue()).intValue()); } break; - case "maxWorkerExecuteTime": - if (member.getValue() instanceof Number) { - obj.setMaxWorkerExecuteTime(((Number)member.getValue()).longValue()); - } - break; - case "maxWorkerExecuteTimeUnit": - if (member.getValue() instanceof String) { - obj.setMaxWorkerExecuteTimeUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); - } - break; - case "worker": - if (member.getValue() instanceof Boolean) { - obj.setWorker((Boolean)member.getValue()); - } - break; case "workerPoolName": if (member.getValue() instanceof String) { obj.setWorkerPoolName((String)member.getValue()); @@ -60,6 +50,16 @@ static void fromJson(Iterable> json, Deploym obj.setWorkerPoolSize(((Number)member.getValue()).intValue()); } break; + case "maxWorkerExecuteTime": + if (member.getValue() instanceof Number) { + obj.setMaxWorkerExecuteTime(((Number)member.getValue()).longValue()); + } + break; + case "maxWorkerExecuteTimeUnit": + if (member.getValue() instanceof String) { + obj.setMaxWorkerExecuteTimeUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); + } + break; } } } @@ -72,16 +72,18 @@ static void toJson(DeploymentOptions obj, java.util.Map json) { if (obj.getConfig() != null) { json.put("config", obj.getConfig()); } + if (obj.getThreadingModel() != null) { + json.put("threadingModel", obj.getThreadingModel().name()); + } json.put("ha", obj.isHa()); json.put("instances", obj.getInstances()); - json.put("maxWorkerExecuteTime", obj.getMaxWorkerExecuteTime()); - if (obj.getMaxWorkerExecuteTimeUnit() != null) { - json.put("maxWorkerExecuteTimeUnit", obj.getMaxWorkerExecuteTimeUnit().name()); - } - json.put("worker", obj.isWorker()); if (obj.getWorkerPoolName() != null) { json.put("workerPoolName", obj.getWorkerPoolName()); } json.put("workerPoolSize", obj.getWorkerPoolSize()); + json.put("maxWorkerExecuteTime", obj.getMaxWorkerExecuteTime()); + if (obj.getMaxWorkerExecuteTimeUnit() != null) { + json.put("maxWorkerExecuteTimeUnit", obj.getMaxWorkerExecuteTimeUnit().name()); + } } } diff --git a/src/main/generated/io/vertx/core/VertxOptionsConverter.java b/src/main/generated/io/vertx/core/VertxOptionsConverter.java index 07d0abf6620..d05361e5a8f 100644 --- a/src/main/generated/io/vertx/core/VertxOptionsConverter.java +++ b/src/main/generated/io/vertx/core/VertxOptionsConverter.java @@ -20,39 +20,34 @@ public class VertxOptionsConverter { static void fromJson(Iterable> json, VertxOptions obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { - case "addressResolverOptions": - if (member.getValue() instanceof JsonObject) { - obj.setAddressResolverOptions(new io.vertx.core.dns.AddressResolverOptions((io.vertx.core.json.JsonObject)member.getValue())); - } - break; - case "blockedThreadCheckInterval": + case "eventLoopPoolSize": if (member.getValue() instanceof Number) { - obj.setBlockedThreadCheckInterval(((Number)member.getValue()).longValue()); + obj.setEventLoopPoolSize(((Number)member.getValue()).intValue()); } break; - case "blockedThreadCheckIntervalUnit": - if (member.getValue() instanceof String) { - obj.setBlockedThreadCheckIntervalUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); + case "workerPoolSize": + if (member.getValue() instanceof Number) { + obj.setWorkerPoolSize(((Number)member.getValue()).intValue()); } break; - case "disableTCCL": - if (member.getValue() instanceof Boolean) { - obj.setDisableTCCL((Boolean)member.getValue()); + case "blockedThreadCheckInterval": + if (member.getValue() instanceof Number) { + obj.setBlockedThreadCheckInterval(((Number)member.getValue()).longValue()); } break; - case "eventBusOptions": - if (member.getValue() instanceof JsonObject) { - obj.setEventBusOptions(new io.vertx.core.eventbus.EventBusOptions((io.vertx.core.json.JsonObject)member.getValue())); + case "maxEventLoopExecuteTime": + if (member.getValue() instanceof Number) { + obj.setMaxEventLoopExecuteTime(((Number)member.getValue()).longValue()); } break; - case "eventLoopPoolSize": + case "maxWorkerExecuteTime": if (member.getValue() instanceof Number) { - obj.setEventLoopPoolSize(((Number)member.getValue()).intValue()); + obj.setMaxWorkerExecuteTime(((Number)member.getValue()).longValue()); } break; - case "fileSystemOptions": - if (member.getValue() instanceof JsonObject) { - obj.setFileSystemOptions(new io.vertx.core.file.FileSystemOptions((io.vertx.core.json.JsonObject)member.getValue())); + case "internalBlockingPoolSize": + if (member.getValue() instanceof Number) { + obj.setInternalBlockingPoolSize(((Number)member.getValue()).intValue()); } break; case "haEnabled": @@ -60,39 +55,39 @@ static void fromJson(Iterable> json, VertxOp obj.setHAEnabled((Boolean)member.getValue()); } break; + case "quorumSize": + if (member.getValue() instanceof Number) { + obj.setQuorumSize(((Number)member.getValue()).intValue()); + } + break; case "haGroup": if (member.getValue() instanceof String) { obj.setHAGroup((String)member.getValue()); } break; - case "internalBlockingPoolSize": - if (member.getValue() instanceof Number) { - obj.setInternalBlockingPoolSize(((Number)member.getValue()).intValue()); - } - break; - case "maxEventLoopExecuteTime": - if (member.getValue() instanceof Number) { - obj.setMaxEventLoopExecuteTime(((Number)member.getValue()).longValue()); + case "metricsOptions": + if (member.getValue() instanceof JsonObject) { + obj.setMetricsOptions(new io.vertx.core.metrics.MetricsOptions((io.vertx.core.json.JsonObject)member.getValue())); } break; - case "maxEventLoopExecuteTimeUnit": - if (member.getValue() instanceof String) { - obj.setMaxEventLoopExecuteTimeUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); + case "fileSystemOptions": + if (member.getValue() instanceof JsonObject) { + obj.setFileSystemOptions(new io.vertx.core.file.FileSystemOptions((io.vertx.core.json.JsonObject)member.getValue())); } break; - case "maxWorkerExecuteTime": + case "warningExceptionTime": if (member.getValue() instanceof Number) { - obj.setMaxWorkerExecuteTime(((Number)member.getValue()).longValue()); + obj.setWarningExceptionTime(((Number)member.getValue()).longValue()); } break; - case "maxWorkerExecuteTimeUnit": - if (member.getValue() instanceof String) { - obj.setMaxWorkerExecuteTimeUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); + case "eventBusOptions": + if (member.getValue() instanceof JsonObject) { + obj.setEventBusOptions(new io.vertx.core.eventbus.EventBusOptions((io.vertx.core.json.JsonObject)member.getValue())); } break; - case "metricsOptions": + case "addressResolverOptions": if (member.getValue() instanceof JsonObject) { - obj.setMetricsOptions(new io.vertx.core.metrics.MetricsOptions((io.vertx.core.json.JsonObject)member.getValue())); + obj.setAddressResolverOptions(new io.vertx.core.dns.AddressResolverOptions((io.vertx.core.json.JsonObject)member.getValue())); } break; case "preferNativeTransport": @@ -100,34 +95,39 @@ static void fromJson(Iterable> json, VertxOp obj.setPreferNativeTransport((Boolean)member.getValue()); } break; - case "quorumSize": - if (member.getValue() instanceof Number) { - obj.setQuorumSize(((Number)member.getValue()).intValue()); + case "maxEventLoopExecuteTimeUnit": + if (member.getValue() instanceof String) { + obj.setMaxEventLoopExecuteTimeUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); } break; - case "tracingOptions": - if (member.getValue() instanceof JsonObject) { - obj.setTracingOptions(new io.vertx.core.tracing.TracingOptions((io.vertx.core.json.JsonObject)member.getValue())); + case "maxWorkerExecuteTimeUnit": + if (member.getValue() instanceof String) { + obj.setMaxWorkerExecuteTimeUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); } break; - case "useDaemonThread": - if (member.getValue() instanceof Boolean) { - obj.setUseDaemonThread((Boolean)member.getValue()); + case "warningExceptionTimeUnit": + if (member.getValue() instanceof String) { + obj.setWarningExceptionTimeUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); } break; - case "warningExceptionTime": - if (member.getValue() instanceof Number) { - obj.setWarningExceptionTime(((Number)member.getValue()).longValue()); + case "blockedThreadCheckIntervalUnit": + if (member.getValue() instanceof String) { + obj.setBlockedThreadCheckIntervalUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); } break; - case "warningExceptionTimeUnit": - if (member.getValue() instanceof String) { - obj.setWarningExceptionTimeUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); + case "tracingOptions": + if (member.getValue() instanceof JsonObject) { + obj.setTracingOptions(new io.vertx.core.tracing.TracingOptions((io.vertx.core.json.JsonObject)member.getValue())); } break; - case "workerPoolSize": - if (member.getValue() instanceof Number) { - obj.setWorkerPoolSize(((Number)member.getValue()).intValue()); + case "disableTCCL": + if (member.getValue() instanceof Boolean) { + obj.setDisableTCCL((Boolean)member.getValue()); + } + break; + case "useDaemonThread": + if (member.getValue() instanceof Boolean) { + obj.setUseDaemonThread((Boolean)member.getValue()); } break; } @@ -139,49 +139,49 @@ static void toJson(VertxOptions obj, JsonObject json) { } static void toJson(VertxOptions obj, java.util.Map json) { - if (obj.getAddressResolverOptions() != null) { - json.put("addressResolverOptions", obj.getAddressResolverOptions().toJson()); - } + json.put("eventLoopPoolSize", obj.getEventLoopPoolSize()); + json.put("workerPoolSize", obj.getWorkerPoolSize()); json.put("blockedThreadCheckInterval", obj.getBlockedThreadCheckInterval()); - if (obj.getBlockedThreadCheckIntervalUnit() != null) { - json.put("blockedThreadCheckIntervalUnit", obj.getBlockedThreadCheckIntervalUnit().name()); + json.put("maxEventLoopExecuteTime", obj.getMaxEventLoopExecuteTime()); + json.put("maxWorkerExecuteTime", obj.getMaxWorkerExecuteTime()); + json.put("internalBlockingPoolSize", obj.getInternalBlockingPoolSize()); + json.put("haEnabled", obj.isHAEnabled()); + json.put("quorumSize", obj.getQuorumSize()); + if (obj.getHAGroup() != null) { + json.put("haGroup", obj.getHAGroup()); } - json.put("disableTCCL", obj.getDisableTCCL()); - if (obj.getEventBusOptions() != null) { - json.put("eventBusOptions", obj.getEventBusOptions().toJson()); + if (obj.getMetricsOptions() != null) { + json.put("metricsOptions", obj.getMetricsOptions().toJson()); } - json.put("eventLoopPoolSize", obj.getEventLoopPoolSize()); if (obj.getFileSystemOptions() != null) { json.put("fileSystemOptions", obj.getFileSystemOptions().toJson()); } - json.put("haEnabled", obj.isHAEnabled()); - if (obj.getHAGroup() != null) { - json.put("haGroup", obj.getHAGroup()); + json.put("warningExceptionTime", obj.getWarningExceptionTime()); + if (obj.getEventBusOptions() != null) { + json.put("eventBusOptions", obj.getEventBusOptions().toJson()); } - json.put("internalBlockingPoolSize", obj.getInternalBlockingPoolSize()); - json.put("maxEventLoopExecuteTime", obj.getMaxEventLoopExecuteTime()); + if (obj.getAddressResolverOptions() != null) { + json.put("addressResolverOptions", obj.getAddressResolverOptions().toJson()); + } + json.put("preferNativeTransport", obj.getPreferNativeTransport()); if (obj.getMaxEventLoopExecuteTimeUnit() != null) { json.put("maxEventLoopExecuteTimeUnit", obj.getMaxEventLoopExecuteTimeUnit().name()); } - json.put("maxWorkerExecuteTime", obj.getMaxWorkerExecuteTime()); if (obj.getMaxWorkerExecuteTimeUnit() != null) { json.put("maxWorkerExecuteTimeUnit", obj.getMaxWorkerExecuteTimeUnit().name()); } - if (obj.getMetricsOptions() != null) { - json.put("metricsOptions", obj.getMetricsOptions().toJson()); + if (obj.getWarningExceptionTimeUnit() != null) { + json.put("warningExceptionTimeUnit", obj.getWarningExceptionTimeUnit().name()); + } + if (obj.getBlockedThreadCheckIntervalUnit() != null) { + json.put("blockedThreadCheckIntervalUnit", obj.getBlockedThreadCheckIntervalUnit().name()); } - json.put("preferNativeTransport", obj.getPreferNativeTransport()); - json.put("quorumSize", obj.getQuorumSize()); if (obj.getTracingOptions() != null) { json.put("tracingOptions", obj.getTracingOptions().toJson()); } + json.put("disableTCCL", obj.getDisableTCCL()); if (obj.getUseDaemonThread() != null) { json.put("useDaemonThread", obj.getUseDaemonThread()); } - json.put("warningExceptionTime", obj.getWarningExceptionTime()); - if (obj.getWarningExceptionTimeUnit() != null) { - json.put("warningExceptionTimeUnit", obj.getWarningExceptionTimeUnit().name()); - } - json.put("workerPoolSize", obj.getWorkerPoolSize()); } } diff --git a/src/main/generated/io/vertx/core/cli/ArgumentConverter.java b/src/main/generated/io/vertx/core/cli/ArgumentConverter.java deleted file mode 100644 index 7ba919378a8..00000000000 --- a/src/main/generated/io/vertx/core/cli/ArgumentConverter.java +++ /dev/null @@ -1,81 +0,0 @@ -package io.vertx.core.cli; - -import io.vertx.core.json.JsonObject; -import io.vertx.core.json.JsonArray; -import io.vertx.core.json.impl.JsonUtil; -import java.time.Instant; -import java.time.format.DateTimeFormatter; -import java.util.Base64; - -/** - * Converter and mapper for {@link io.vertx.core.cli.Argument}. - * NOTE: This class has been automatically generated from the {@link io.vertx.core.cli.Argument} original class using Vert.x codegen. - */ -public class ArgumentConverter { - - - private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER; - private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER; - - static void fromJson(Iterable> json, Argument obj) { - for (java.util.Map.Entry member : json) { - switch (member.getKey()) { - case "argName": - if (member.getValue() instanceof String) { - obj.setArgName((String)member.getValue()); - } - break; - case "defaultValue": - if (member.getValue() instanceof String) { - obj.setDefaultValue((String)member.getValue()); - } - break; - case "description": - if (member.getValue() instanceof String) { - obj.setDescription((String)member.getValue()); - } - break; - case "hidden": - if (member.getValue() instanceof Boolean) { - obj.setHidden((Boolean)member.getValue()); - } - break; - case "index": - if (member.getValue() instanceof Number) { - obj.setIndex(((Number)member.getValue()).intValue()); - } - break; - case "multiValued": - if (member.getValue() instanceof Boolean) { - obj.setMultiValued((Boolean)member.getValue()); - } - break; - case "required": - if (member.getValue() instanceof Boolean) { - obj.setRequired((Boolean)member.getValue()); - } - break; - } - } - } - - static void toJson(Argument obj, JsonObject json) { - toJson(obj, json.getMap()); - } - - static void toJson(Argument obj, java.util.Map json) { - if (obj.getArgName() != null) { - json.put("argName", obj.getArgName()); - } - if (obj.getDefaultValue() != null) { - json.put("defaultValue", obj.getDefaultValue()); - } - if (obj.getDescription() != null) { - json.put("description", obj.getDescription()); - } - json.put("hidden", obj.isHidden()); - json.put("index", obj.getIndex()); - json.put("multiValued", obj.isMultiValued()); - json.put("required", obj.isRequired()); - } -} diff --git a/src/main/generated/io/vertx/core/cli/OptionConverter.java b/src/main/generated/io/vertx/core/cli/OptionConverter.java deleted file mode 100644 index ad97d29e9e1..00000000000 --- a/src/main/generated/io/vertx/core/cli/OptionConverter.java +++ /dev/null @@ -1,129 +0,0 @@ -package io.vertx.core.cli; - -import io.vertx.core.json.JsonObject; -import io.vertx.core.json.JsonArray; -import io.vertx.core.json.impl.JsonUtil; -import java.time.Instant; -import java.time.format.DateTimeFormatter; -import java.util.Base64; - -/** - * Converter and mapper for {@link io.vertx.core.cli.Option}. - * NOTE: This class has been automatically generated from the {@link io.vertx.core.cli.Option} original class using Vert.x codegen. - */ -public class OptionConverter { - - - private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER; - private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER; - - static void fromJson(Iterable> json, Option obj) { - for (java.util.Map.Entry member : json) { - switch (member.getKey()) { - case "argName": - if (member.getValue() instanceof String) { - obj.setArgName((String)member.getValue()); - } - break; - case "choices": - if (member.getValue() instanceof JsonArray) { - java.util.LinkedHashSet list = new java.util.LinkedHashSet<>(); - ((Iterable)member.getValue()).forEach( item -> { - if (item instanceof String) - list.add((String)item); - }); - obj.setChoices(list); - } - break; - case "defaultValue": - if (member.getValue() instanceof String) { - obj.setDefaultValue((String)member.getValue()); - } - break; - case "description": - if (member.getValue() instanceof String) { - obj.setDescription((String)member.getValue()); - } - break; - case "flag": - if (member.getValue() instanceof Boolean) { - obj.setFlag((Boolean)member.getValue()); - } - break; - case "help": - if (member.getValue() instanceof Boolean) { - obj.setHelp((Boolean)member.getValue()); - } - break; - case "hidden": - if (member.getValue() instanceof Boolean) { - obj.setHidden((Boolean)member.getValue()); - } - break; - case "longName": - if (member.getValue() instanceof String) { - obj.setLongName((String)member.getValue()); - } - break; - case "multiValued": - if (member.getValue() instanceof Boolean) { - obj.setMultiValued((Boolean)member.getValue()); - } - break; - case "name": - break; - case "required": - if (member.getValue() instanceof Boolean) { - obj.setRequired((Boolean)member.getValue()); - } - break; - case "shortName": - if (member.getValue() instanceof String) { - obj.setShortName((String)member.getValue()); - } - break; - case "singleValued": - if (member.getValue() instanceof Boolean) { - obj.setSingleValued((Boolean)member.getValue()); - } - break; - } - } - } - - static void toJson(Option obj, JsonObject json) { - toJson(obj, json.getMap()); - } - - static void toJson(Option obj, java.util.Map json) { - if (obj.getArgName() != null) { - json.put("argName", obj.getArgName()); - } - if (obj.getChoices() != null) { - JsonArray array = new JsonArray(); - obj.getChoices().forEach(item -> array.add(item)); - json.put("choices", array); - } - if (obj.getDefaultValue() != null) { - json.put("defaultValue", obj.getDefaultValue()); - } - if (obj.getDescription() != null) { - json.put("description", obj.getDescription()); - } - json.put("flag", obj.isFlag()); - json.put("help", obj.isHelp()); - json.put("hidden", obj.isHidden()); - if (obj.getLongName() != null) { - json.put("longName", obj.getLongName()); - } - json.put("multiValued", obj.isMultiValued()); - if (obj.getName() != null) { - json.put("name", obj.getName()); - } - json.put("required", obj.isRequired()); - if (obj.getShortName() != null) { - json.put("shortName", obj.getShortName()); - } - json.put("singleValued", obj.isSingleValued()); - } -} diff --git a/src/main/generated/io/vertx/core/datagram/DatagramSocketOptionsConverter.java b/src/main/generated/io/vertx/core/datagram/DatagramSocketOptionsConverter.java index 17befade61d..d1142a0bea7 100644 --- a/src/main/generated/io/vertx/core/datagram/DatagramSocketOptionsConverter.java +++ b/src/main/generated/io/vertx/core/datagram/DatagramSocketOptionsConverter.java @@ -25,24 +25,24 @@ static void fromJson(Iterable> json, Datagra obj.setBroadcast((Boolean)member.getValue()); } break; - case "ipV6": - if (member.getValue() instanceof Boolean) { - obj.setIpV6((Boolean)member.getValue()); - } - break; case "loopbackModeDisabled": if (member.getValue() instanceof Boolean) { obj.setLoopbackModeDisabled((Boolean)member.getValue()); } break; + case "multicastTimeToLive": + if (member.getValue() instanceof Number) { + obj.setMulticastTimeToLive(((Number)member.getValue()).intValue()); + } + break; case "multicastNetworkInterface": if (member.getValue() instanceof String) { obj.setMulticastNetworkInterface((String)member.getValue()); } break; - case "multicastTimeToLive": - if (member.getValue() instanceof Number) { - obj.setMulticastTimeToLive(((Number)member.getValue()).intValue()); + case "ipV6": + if (member.getValue() instanceof Boolean) { + obj.setIpV6((Boolean)member.getValue()); } break; } @@ -55,11 +55,11 @@ static void toJson(DatagramSocketOptions obj, JsonObject json) { static void toJson(DatagramSocketOptions obj, java.util.Map json) { json.put("broadcast", obj.isBroadcast()); - json.put("ipV6", obj.isIpV6()); json.put("loopbackModeDisabled", obj.isLoopbackModeDisabled()); + json.put("multicastTimeToLive", obj.getMulticastTimeToLive()); if (obj.getMulticastNetworkInterface() != null) { json.put("multicastNetworkInterface", obj.getMulticastNetworkInterface()); } - json.put("multicastTimeToLive", obj.getMulticastTimeToLive()); + json.put("ipV6", obj.isIpV6()); } } diff --git a/src/main/generated/io/vertx/core/dns/AddressResolverOptionsConverter.java b/src/main/generated/io/vertx/core/dns/AddressResolverOptionsConverter.java index c4cee6a673d..67e7c0f48be 100644 --- a/src/main/generated/io/vertx/core/dns/AddressResolverOptionsConverter.java +++ b/src/main/generated/io/vertx/core/dns/AddressResolverOptionsConverter.java @@ -20,21 +20,6 @@ public class AddressResolverOptionsConverter { static void fromJson(Iterable> json, AddressResolverOptions obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { - case "cacheMaxTimeToLive": - if (member.getValue() instanceof Number) { - obj.setCacheMaxTimeToLive(((Number)member.getValue()).intValue()); - } - break; - case "cacheMinTimeToLive": - if (member.getValue() instanceof Number) { - obj.setCacheMinTimeToLive(((Number)member.getValue()).intValue()); - } - break; - case "cacheNegativeTimeToLive": - if (member.getValue() instanceof Number) { - obj.setCacheNegativeTimeToLive(((Number)member.getValue()).intValue()); - } - break; case "hostsPath": if (member.getValue() instanceof String) { obj.setHostsPath((String)member.getValue()); @@ -45,14 +30,19 @@ static void fromJson(Iterable> json, Address obj.setHostsValue(io.vertx.core.buffer.Buffer.buffer(BASE64_DECODER.decode((String)member.getValue()))); } break; - case "maxQueries": + case "hostsRefreshPeriod": if (member.getValue() instanceof Number) { - obj.setMaxQueries(((Number)member.getValue()).intValue()); + obj.setHostsRefreshPeriod(((Number)member.getValue()).intValue()); } break; - case "ndots": - if (member.getValue() instanceof Number) { - obj.setNdots(((Number)member.getValue()).intValue()); + case "servers": + if (member.getValue() instanceof JsonArray) { + java.util.ArrayList list = new java.util.ArrayList<>(); + ((Iterable)member.getValue()).forEach( item -> { + if (item instanceof String) + list.add((String)item); + }); + obj.setServers(list); } break; case "optResourceEnabled": @@ -60,24 +50,34 @@ static void fromJson(Iterable> json, Address obj.setOptResourceEnabled((Boolean)member.getValue()); } break; - case "queryTimeout": + case "cacheMinTimeToLive": if (member.getValue() instanceof Number) { - obj.setQueryTimeout(((Number)member.getValue()).longValue()); + obj.setCacheMinTimeToLive(((Number)member.getValue()).intValue()); } break; - case "rdFlag": - if (member.getValue() instanceof Boolean) { - obj.setRdFlag((Boolean)member.getValue()); + case "cacheMaxTimeToLive": + if (member.getValue() instanceof Number) { + obj.setCacheMaxTimeToLive(((Number)member.getValue()).intValue()); } break; - case "rotateServers": - if (member.getValue() instanceof Boolean) { - obj.setRotateServers((Boolean)member.getValue()); + case "cacheNegativeTimeToLive": + if (member.getValue() instanceof Number) { + obj.setCacheNegativeTimeToLive(((Number)member.getValue()).intValue()); } break; - case "roundRobinInetAddress": + case "queryTimeout": + if (member.getValue() instanceof Number) { + obj.setQueryTimeout(((Number)member.getValue()).longValue()); + } + break; + case "maxQueries": + if (member.getValue() instanceof Number) { + obj.setMaxQueries(((Number)member.getValue()).intValue()); + } + break; + case "rdFlag": if (member.getValue() instanceof Boolean) { - obj.setRoundRobinInetAddress((Boolean)member.getValue()); + obj.setRdFlag((Boolean)member.getValue()); } break; case "searchDomains": @@ -90,14 +90,19 @@ static void fromJson(Iterable> json, Address obj.setSearchDomains(list); } break; - case "servers": - if (member.getValue() instanceof JsonArray) { - java.util.ArrayList list = new java.util.ArrayList<>(); - ((Iterable)member.getValue()).forEach( item -> { - if (item instanceof String) - list.add((String)item); - }); - obj.setServers(list); + case "ndots": + if (member.getValue() instanceof Number) { + obj.setNdots(((Number)member.getValue()).intValue()); + } + break; + case "rotateServers": + if (member.getValue() instanceof Boolean) { + obj.setRotateServers((Boolean)member.getValue()); + } + break; + case "roundRobinInetAddress": + if (member.getValue() instanceof Boolean) { + obj.setRoundRobinInetAddress((Boolean)member.getValue()); } break; } @@ -109,31 +114,32 @@ static void toJson(AddressResolverOptions obj, JsonObject json) { } static void toJson(AddressResolverOptions obj, java.util.Map json) { - json.put("cacheMaxTimeToLive", obj.getCacheMaxTimeToLive()); - json.put("cacheMinTimeToLive", obj.getCacheMinTimeToLive()); - json.put("cacheNegativeTimeToLive", obj.getCacheNegativeTimeToLive()); if (obj.getHostsPath() != null) { json.put("hostsPath", obj.getHostsPath()); } if (obj.getHostsValue() != null) { json.put("hostsValue", BASE64_ENCODER.encodeToString(obj.getHostsValue().getBytes())); } - json.put("maxQueries", obj.getMaxQueries()); - json.put("ndots", obj.getNdots()); + json.put("hostsRefreshPeriod", obj.getHostsRefreshPeriod()); + if (obj.getServers() != null) { + JsonArray array = new JsonArray(); + obj.getServers().forEach(item -> array.add(item)); + json.put("servers", array); + } json.put("optResourceEnabled", obj.isOptResourceEnabled()); + json.put("cacheMinTimeToLive", obj.getCacheMinTimeToLive()); + json.put("cacheMaxTimeToLive", obj.getCacheMaxTimeToLive()); + json.put("cacheNegativeTimeToLive", obj.getCacheNegativeTimeToLive()); json.put("queryTimeout", obj.getQueryTimeout()); + json.put("maxQueries", obj.getMaxQueries()); json.put("rdFlag", obj.getRdFlag()); - json.put("rotateServers", obj.isRotateServers()); - json.put("roundRobinInetAddress", obj.isRoundRobinInetAddress()); if (obj.getSearchDomains() != null) { JsonArray array = new JsonArray(); obj.getSearchDomains().forEach(item -> array.add(item)); json.put("searchDomains", array); } - if (obj.getServers() != null) { - JsonArray array = new JsonArray(); - obj.getServers().forEach(item -> array.add(item)); - json.put("servers", array); - } + json.put("ndots", obj.getNdots()); + json.put("rotateServers", obj.isRotateServers()); + json.put("roundRobinInetAddress", obj.isRoundRobinInetAddress()); } } diff --git a/src/main/generated/io/vertx/core/dns/DnsClientOptionsConverter.java b/src/main/generated/io/vertx/core/dns/DnsClientOptionsConverter.java index 91a4ff971dc..d6351c0a56e 100644 --- a/src/main/generated/io/vertx/core/dns/DnsClientOptionsConverter.java +++ b/src/main/generated/io/vertx/core/dns/DnsClientOptionsConverter.java @@ -17,12 +17,12 @@ public class DnsClientOptionsConverter { private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER; private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER; - public static void fromJson(Iterable> json, DnsClientOptions obj) { + static void fromJson(Iterable> json, DnsClientOptions obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { - case "activityLogFormat": - if (member.getValue() instanceof String) { - obj.setActivityLogFormat(io.netty.handler.logging.ByteBufFormat.valueOf((String)member.getValue())); + case "port": + if (member.getValue() instanceof Number) { + obj.setPort(((Number)member.getValue()).intValue()); } break; case "host": @@ -30,19 +30,19 @@ public static void fromJson(Iterable> json, obj.setHost((String)member.getValue()); } break; + case "queryTimeout": + if (member.getValue() instanceof Number) { + obj.setQueryTimeout(((Number)member.getValue()).longValue()); + } + break; case "logActivity": if (member.getValue() instanceof Boolean) { obj.setLogActivity((Boolean)member.getValue()); } break; - case "port": - if (member.getValue() instanceof Number) { - obj.setPort(((Number)member.getValue()).intValue()); - } - break; - case "queryTimeout": - if (member.getValue() instanceof Number) { - obj.setQueryTimeout(((Number)member.getValue()).longValue()); + case "activityLogFormat": + if (member.getValue() instanceof String) { + obj.setActivityLogFormat(io.netty.handler.logging.ByteBufFormat.valueOf((String)member.getValue())); } break; case "recursionDesired": @@ -54,20 +54,20 @@ public static void fromJson(Iterable> json, } } - public static void toJson(DnsClientOptions obj, JsonObject json) { + static void toJson(DnsClientOptions obj, JsonObject json) { toJson(obj, json.getMap()); } - public static void toJson(DnsClientOptions obj, java.util.Map json) { - if (obj.getActivityLogFormat() != null) { - json.put("activityLogFormat", obj.getActivityLogFormat().name()); - } + static void toJson(DnsClientOptions obj, java.util.Map json) { + json.put("port", obj.getPort()); if (obj.getHost() != null) { json.put("host", obj.getHost()); } - json.put("logActivity", obj.getLogActivity()); - json.put("port", obj.getPort()); json.put("queryTimeout", obj.getQueryTimeout()); + json.put("logActivity", obj.getLogActivity()); + if (obj.getActivityLogFormat() != null) { + json.put("activityLogFormat", obj.getActivityLogFormat().name()); + } json.put("recursionDesired", obj.isRecursionDesired()); } } diff --git a/src/main/generated/io/vertx/core/eventbus/EventBusOptionsConverter.java b/src/main/generated/io/vertx/core/eventbus/EventBusOptionsConverter.java index 2eae88ac055..85034de88ec 100644 --- a/src/main/generated/io/vertx/core/eventbus/EventBusOptionsConverter.java +++ b/src/main/generated/io/vertx/core/eventbus/EventBusOptionsConverter.java @@ -20,83 +20,14 @@ public class EventBusOptionsConverter { static void fromJson(Iterable> json, EventBusOptions obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { - case "acceptBacklog": - if (member.getValue() instanceof Number) { - obj.setAcceptBacklog(((Number)member.getValue()).intValue()); - } - break; - case "activityLogDataFormat": - if (member.getValue() instanceof String) { - obj.setActivityLogDataFormat(io.netty.handler.logging.ByteBufFormat.valueOf((String)member.getValue())); - } - break; case "clientAuth": if (member.getValue() instanceof String) { obj.setClientAuth(io.vertx.core.http.ClientAuth.valueOf((String)member.getValue())); } break; - case "clusterNodeMetadata": - if (member.getValue() instanceof JsonObject) { - obj.setClusterNodeMetadata(((JsonObject)member.getValue()).copy()); - } - break; - case "clusterPingInterval": - if (member.getValue() instanceof Number) { - obj.setClusterPingInterval(((Number)member.getValue()).longValue()); - } - break; - case "clusterPingReplyInterval": - if (member.getValue() instanceof Number) { - obj.setClusterPingReplyInterval(((Number)member.getValue()).longValue()); - } - break; - case "clusterPublicHost": - if (member.getValue() instanceof String) { - obj.setClusterPublicHost((String)member.getValue()); - } - break; - case "clusterPublicPort": - if (member.getValue() instanceof Number) { - obj.setClusterPublicPort(((Number)member.getValue()).intValue()); - } - break; - case "connectTimeout": + case "acceptBacklog": if (member.getValue() instanceof Number) { - obj.setConnectTimeout(((Number)member.getValue()).intValue()); - } - break; - case "crlPaths": - if (member.getValue() instanceof JsonArray) { - ((Iterable)member.getValue()).forEach( item -> { - if (item instanceof String) - obj.addCrlPath((String)item); - }); - } - break; - case "crlValues": - if (member.getValue() instanceof JsonArray) { - ((Iterable)member.getValue()).forEach( item -> { - if (item instanceof String) - obj.addCrlValue(io.vertx.core.buffer.Buffer.buffer(BASE64_DECODER.decode((String)item))); - }); - } - break; - case "enabledCipherSuites": - if (member.getValue() instanceof JsonArray) { - ((Iterable)member.getValue()).forEach( item -> { - if (item instanceof String) - obj.addEnabledCipherSuite((String)item); - }); - } - break; - case "enabledSecureTransportProtocols": - if (member.getValue() instanceof JsonArray) { - java.util.LinkedHashSet list = new java.util.LinkedHashSet<>(); - ((Iterable)member.getValue()).forEach( item -> { - if (item instanceof String) - list.add((String)item); - }); - obj.setEnabledSecureTransportProtocols(list); + obj.setAcceptBacklog(((Number)member.getValue()).intValue()); } break; case "host": @@ -104,71 +35,11 @@ static void fromJson(Iterable> json, EventBu obj.setHost((String)member.getValue()); } break; - case "idleTimeout": - if (member.getValue() instanceof Number) { - obj.setIdleTimeout(((Number)member.getValue()).intValue()); - } - break; - case "idleTimeoutUnit": - if (member.getValue() instanceof String) { - obj.setIdleTimeoutUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); - } - break; - case "jdkSslEngineOptions": - if (member.getValue() instanceof JsonObject) { - obj.setJdkSslEngineOptions(new io.vertx.core.net.JdkSSLEngineOptions((io.vertx.core.json.JsonObject)member.getValue())); - } - break; - case "keyStoreOptions": - if (member.getValue() instanceof JsonObject) { - obj.setKeyStoreOptions(new io.vertx.core.net.JksOptions((io.vertx.core.json.JsonObject)member.getValue())); - } - break; - case "logActivity": - if (member.getValue() instanceof Boolean) { - obj.setLogActivity((Boolean)member.getValue()); - } - break; - case "openSslEngineOptions": - if (member.getValue() instanceof JsonObject) { - obj.setOpenSslEngineOptions(new io.vertx.core.net.OpenSSLEngineOptions((io.vertx.core.json.JsonObject)member.getValue())); - } - break; - case "pemKeyCertOptions": - if (member.getValue() instanceof JsonObject) { - obj.setPemKeyCertOptions(new io.vertx.core.net.PemKeyCertOptions((io.vertx.core.json.JsonObject)member.getValue())); - } - break; - case "pemTrustOptions": - if (member.getValue() instanceof JsonObject) { - obj.setPemTrustOptions(new io.vertx.core.net.PemTrustOptions((io.vertx.core.json.JsonObject)member.getValue())); - } - break; - case "pfxKeyCertOptions": - if (member.getValue() instanceof JsonObject) { - obj.setPfxKeyCertOptions(new io.vertx.core.net.PfxOptions((io.vertx.core.json.JsonObject)member.getValue())); - } - break; - case "pfxTrustOptions": - if (member.getValue() instanceof JsonObject) { - obj.setPfxTrustOptions(new io.vertx.core.net.PfxOptions((io.vertx.core.json.JsonObject)member.getValue())); - } - break; case "port": if (member.getValue() instanceof Number) { obj.setPort(((Number)member.getValue()).intValue()); } break; - case "readIdleTimeout": - if (member.getValue() instanceof Number) { - obj.setReadIdleTimeout(((Number)member.getValue()).intValue()); - } - break; - case "receiveBufferSize": - if (member.getValue() instanceof Number) { - obj.setReceiveBufferSize(((Number)member.getValue()).intValue()); - } - break; case "reconnectAttempts": if (member.getValue() instanceof Number) { obj.setReconnectAttempts(((Number)member.getValue()).intValue()); @@ -179,94 +50,39 @@ static void fromJson(Iterable> json, EventBu obj.setReconnectInterval(((Number)member.getValue()).longValue()); } break; - case "reuseAddress": - if (member.getValue() instanceof Boolean) { - obj.setReuseAddress((Boolean)member.getValue()); - } - break; - case "reusePort": + case "trustAll": if (member.getValue() instanceof Boolean) { - obj.setReusePort((Boolean)member.getValue()); + obj.setTrustAll((Boolean)member.getValue()); } break; - case "sendBufferSize": + case "connectTimeout": if (member.getValue() instanceof Number) { - obj.setSendBufferSize(((Number)member.getValue()).intValue()); + obj.setConnectTimeout(((Number)member.getValue()).intValue()); } break; - case "soLinger": + case "clusterPingInterval": if (member.getValue() instanceof Number) { - obj.setSoLinger(((Number)member.getValue()).intValue()); - } - break; - case "ssl": - if (member.getValue() instanceof Boolean) { - obj.setSsl((Boolean)member.getValue()); + obj.setClusterPingInterval(((Number)member.getValue()).longValue()); } break; - case "sslHandshakeTimeout": + case "clusterPingReplyInterval": if (member.getValue() instanceof Number) { - obj.setSslHandshakeTimeout(((Number)member.getValue()).longValue()); + obj.setClusterPingReplyInterval(((Number)member.getValue()).longValue()); } break; - case "sslHandshakeTimeoutUnit": + case "clusterPublicHost": if (member.getValue() instanceof String) { - obj.setSslHandshakeTimeoutUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); - } - break; - case "tcpCork": - if (member.getValue() instanceof Boolean) { - obj.setTcpCork((Boolean)member.getValue()); - } - break; - case "tcpFastOpen": - if (member.getValue() instanceof Boolean) { - obj.setTcpFastOpen((Boolean)member.getValue()); - } - break; - case "tcpKeepAlive": - if (member.getValue() instanceof Boolean) { - obj.setTcpKeepAlive((Boolean)member.getValue()); - } - break; - case "tcpNoDelay": - if (member.getValue() instanceof Boolean) { - obj.setTcpNoDelay((Boolean)member.getValue()); - } - break; - case "tcpQuickAck": - if (member.getValue() instanceof Boolean) { - obj.setTcpQuickAck((Boolean)member.getValue()); - } - break; - case "tcpUserTimeout": - if (member.getValue() instanceof Number) { - obj.setTcpUserTimeout(((Number)member.getValue()).intValue()); + obj.setClusterPublicHost((String)member.getValue()); } break; - case "trafficClass": + case "clusterPublicPort": if (member.getValue() instanceof Number) { - obj.setTrafficClass(((Number)member.getValue()).intValue()); - } - break; - case "trustAll": - if (member.getValue() instanceof Boolean) { - obj.setTrustAll((Boolean)member.getValue()); + obj.setClusterPublicPort(((Number)member.getValue()).intValue()); } break; - case "trustStoreOptions": + case "clusterNodeMetadata": if (member.getValue() instanceof JsonObject) { - obj.setTrustStoreOptions(new io.vertx.core.net.JksOptions((io.vertx.core.json.JsonObject)member.getValue())); - } - break; - case "useAlpn": - if (member.getValue() instanceof Boolean) { - obj.setUseAlpn((Boolean)member.getValue()); - } - break; - case "writeIdleTimeout": - if (member.getValue() instanceof Number) { - obj.setWriteIdleTimeout(((Number)member.getValue()).intValue()); + obj.setClusterNodeMetadata(((JsonObject)member.getValue()).copy()); } break; } @@ -278,98 +94,26 @@ static void toJson(EventBusOptions obj, JsonObject json) { } static void toJson(EventBusOptions obj, java.util.Map json) { - json.put("acceptBacklog", obj.getAcceptBacklog()); - if (obj.getActivityLogDataFormat() != null) { - json.put("activityLogDataFormat", obj.getActivityLogDataFormat().name()); - } if (obj.getClientAuth() != null) { json.put("clientAuth", obj.getClientAuth().name()); } - if (obj.getClusterNodeMetadata() != null) { - json.put("clusterNodeMetadata", obj.getClusterNodeMetadata()); - } - json.put("clusterPingInterval", obj.getClusterPingInterval()); - json.put("clusterPingReplyInterval", obj.getClusterPingReplyInterval()); - if (obj.getClusterPublicHost() != null) { - json.put("clusterPublicHost", obj.getClusterPublicHost()); - } - json.put("clusterPublicPort", obj.getClusterPublicPort()); - json.put("connectTimeout", obj.getConnectTimeout()); - if (obj.getCrlPaths() != null) { - JsonArray array = new JsonArray(); - obj.getCrlPaths().forEach(item -> array.add(item)); - json.put("crlPaths", array); - } - if (obj.getCrlValues() != null) { - JsonArray array = new JsonArray(); - obj.getCrlValues().forEach(item -> array.add(BASE64_ENCODER.encodeToString(item.getBytes()))); - json.put("crlValues", array); - } - if (obj.getEnabledCipherSuites() != null) { - JsonArray array = new JsonArray(); - obj.getEnabledCipherSuites().forEach(item -> array.add(item)); - json.put("enabledCipherSuites", array); - } - if (obj.getEnabledSecureTransportProtocols() != null) { - JsonArray array = new JsonArray(); - obj.getEnabledSecureTransportProtocols().forEach(item -> array.add(item)); - json.put("enabledSecureTransportProtocols", array); - } + json.put("acceptBacklog", obj.getAcceptBacklog()); if (obj.getHost() != null) { json.put("host", obj.getHost()); } - json.put("idleTimeout", obj.getIdleTimeout()); - if (obj.getIdleTimeoutUnit() != null) { - json.put("idleTimeoutUnit", obj.getIdleTimeoutUnit().name()); - } - if (obj.getJdkSslEngineOptions() != null) { - json.put("jdkSslEngineOptions", obj.getJdkSslEngineOptions().toJson()); - } - if (obj.getKeyStoreOptions() != null) { - json.put("keyStoreOptions", obj.getKeyStoreOptions().toJson()); - } - json.put("logActivity", obj.getLogActivity()); - if (obj.getOpenSslEngineOptions() != null) { - json.put("openSslEngineOptions", obj.getOpenSslEngineOptions().toJson()); - } - if (obj.getPemKeyCertOptions() != null) { - json.put("pemKeyCertOptions", obj.getPemKeyCertOptions().toJson()); - } - if (obj.getPemTrustOptions() != null) { - json.put("pemTrustOptions", obj.getPemTrustOptions().toJson()); - } - if (obj.getPfxKeyCertOptions() != null) { - json.put("pfxKeyCertOptions", obj.getPfxKeyCertOptions().toJson()); - } - if (obj.getPfxTrustOptions() != null) { - json.put("pfxTrustOptions", obj.getPfxTrustOptions().toJson()); - } json.put("port", obj.getPort()); - json.put("readIdleTimeout", obj.getReadIdleTimeout()); - json.put("receiveBufferSize", obj.getReceiveBufferSize()); json.put("reconnectAttempts", obj.getReconnectAttempts()); json.put("reconnectInterval", obj.getReconnectInterval()); - json.put("reuseAddress", obj.isReuseAddress()); - json.put("reusePort", obj.isReusePort()); - json.put("sendBufferSize", obj.getSendBufferSize()); - json.put("soLinger", obj.getSoLinger()); - json.put("ssl", obj.isSsl()); - json.put("sslHandshakeTimeout", obj.getSslHandshakeTimeout()); - if (obj.getSslHandshakeTimeoutUnit() != null) { - json.put("sslHandshakeTimeoutUnit", obj.getSslHandshakeTimeoutUnit().name()); - } - json.put("tcpCork", obj.isTcpCork()); - json.put("tcpFastOpen", obj.isTcpFastOpen()); - json.put("tcpKeepAlive", obj.isTcpKeepAlive()); - json.put("tcpNoDelay", obj.isTcpNoDelay()); - json.put("tcpQuickAck", obj.isTcpQuickAck()); - json.put("tcpUserTimeout", obj.getTcpUserTimeout()); - json.put("trafficClass", obj.getTrafficClass()); json.put("trustAll", obj.isTrustAll()); - if (obj.getTrustStoreOptions() != null) { - json.put("trustStoreOptions", obj.getTrustStoreOptions().toJson()); + json.put("connectTimeout", obj.getConnectTimeout()); + json.put("clusterPingInterval", obj.getClusterPingInterval()); + json.put("clusterPingReplyInterval", obj.getClusterPingReplyInterval()); + if (obj.getClusterPublicHost() != null) { + json.put("clusterPublicHost", obj.getClusterPublicHost()); + } + json.put("clusterPublicPort", obj.getClusterPublicPort()); + if (obj.getClusterNodeMetadata() != null) { + json.put("clusterNodeMetadata", obj.getClusterNodeMetadata()); } - json.put("useAlpn", obj.isUseAlpn()); - json.put("writeIdleTimeout", obj.getWriteIdleTimeout()); } } diff --git a/src/main/generated/io/vertx/core/file/CopyOptionsConverter.java b/src/main/generated/io/vertx/core/file/CopyOptionsConverter.java index 58dba53227f..b3ebee67e0d 100644 --- a/src/main/generated/io/vertx/core/file/CopyOptionsConverter.java +++ b/src/main/generated/io/vertx/core/file/CopyOptionsConverter.java @@ -20,9 +20,9 @@ public class CopyOptionsConverter { static void fromJson(Iterable> json, CopyOptions obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { - case "atomicMove": + case "replaceExisting": if (member.getValue() instanceof Boolean) { - obj.setAtomicMove((Boolean)member.getValue()); + obj.setReplaceExisting((Boolean)member.getValue()); } break; case "copyAttributes": @@ -30,14 +30,14 @@ static void fromJson(Iterable> json, CopyOpt obj.setCopyAttributes((Boolean)member.getValue()); } break; - case "nofollowLinks": + case "atomicMove": if (member.getValue() instanceof Boolean) { - obj.setNofollowLinks((Boolean)member.getValue()); + obj.setAtomicMove((Boolean)member.getValue()); } break; - case "replaceExisting": + case "nofollowLinks": if (member.getValue() instanceof Boolean) { - obj.setReplaceExisting((Boolean)member.getValue()); + obj.setNofollowLinks((Boolean)member.getValue()); } break; } @@ -49,9 +49,9 @@ static void toJson(CopyOptions obj, JsonObject json) { } static void toJson(CopyOptions obj, java.util.Map json) { - json.put("atomicMove", obj.isAtomicMove()); + json.put("replaceExisting", obj.isReplaceExisting()); json.put("copyAttributes", obj.isCopyAttributes()); + json.put("atomicMove", obj.isAtomicMove()); json.put("nofollowLinks", obj.isNofollowLinks()); - json.put("replaceExisting", obj.isReplaceExisting()); } } diff --git a/src/main/generated/io/vertx/core/file/FileSystemOptionsConverter.java b/src/main/generated/io/vertx/core/file/FileSystemOptionsConverter.java index b75c5235e9f..7135349a29f 100644 --- a/src/main/generated/io/vertx/core/file/FileSystemOptionsConverter.java +++ b/src/main/generated/io/vertx/core/file/FileSystemOptionsConverter.java @@ -25,16 +25,16 @@ static void fromJson(Iterable> json, FileSys obj.setClassPathResolvingEnabled((Boolean)member.getValue()); } break; - case "fileCacheDir": - if (member.getValue() instanceof String) { - obj.setFileCacheDir((String)member.getValue()); - } - break; case "fileCachingEnabled": if (member.getValue() instanceof Boolean) { obj.setFileCachingEnabled((Boolean)member.getValue()); } break; + case "fileCacheDir": + if (member.getValue() instanceof String) { + obj.setFileCacheDir((String)member.getValue()); + } + break; } } } @@ -45,9 +45,9 @@ static void toJson(FileSystemOptions obj, JsonObject json) { static void toJson(FileSystemOptions obj, java.util.Map json) { json.put("classPathResolvingEnabled", obj.isClassPathResolvingEnabled()); + json.put("fileCachingEnabled", obj.isFileCachingEnabled()); if (obj.getFileCacheDir() != null) { json.put("fileCacheDir", obj.getFileCacheDir()); } - json.put("fileCachingEnabled", obj.isFileCachingEnabled()); } } diff --git a/src/main/generated/io/vertx/core/file/OpenOptionsConverter.java b/src/main/generated/io/vertx/core/file/OpenOptionsConverter.java index 2f23d2acabf..ae54db279c7 100644 --- a/src/main/generated/io/vertx/core/file/OpenOptionsConverter.java +++ b/src/main/generated/io/vertx/core/file/OpenOptionsConverter.java @@ -20,9 +20,19 @@ public class OpenOptionsConverter { static void fromJson(Iterable> json, OpenOptions obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { - case "append": + case "perms": + if (member.getValue() instanceof String) { + obj.setPerms((String)member.getValue()); + } + break; + case "read": if (member.getValue() instanceof Boolean) { - obj.setAppend((Boolean)member.getValue()); + obj.setRead((Boolean)member.getValue()); + } + break; + case "write": + if (member.getValue() instanceof Boolean) { + obj.setWrite((Boolean)member.getValue()); } break; case "create": @@ -40,19 +50,9 @@ static void fromJson(Iterable> json, OpenOpt obj.setDeleteOnClose((Boolean)member.getValue()); } break; - case "dsync": - if (member.getValue() instanceof Boolean) { - obj.setDsync((Boolean)member.getValue()); - } - break; - case "perms": - if (member.getValue() instanceof String) { - obj.setPerms((String)member.getValue()); - } - break; - case "read": + case "truncateExisting": if (member.getValue() instanceof Boolean) { - obj.setRead((Boolean)member.getValue()); + obj.setTruncateExisting((Boolean)member.getValue()); } break; case "sparse": @@ -65,14 +65,14 @@ static void fromJson(Iterable> json, OpenOpt obj.setSync((Boolean)member.getValue()); } break; - case "truncateExisting": + case "dsync": if (member.getValue() instanceof Boolean) { - obj.setTruncateExisting((Boolean)member.getValue()); + obj.setDsync((Boolean)member.getValue()); } break; - case "write": + case "append": if (member.getValue() instanceof Boolean) { - obj.setWrite((Boolean)member.getValue()); + obj.setAppend((Boolean)member.getValue()); } break; } @@ -84,18 +84,18 @@ static void toJson(OpenOptions obj, JsonObject json) { } static void toJson(OpenOptions obj, java.util.Map json) { - json.put("append", obj.isAppend()); - json.put("create", obj.isCreate()); - json.put("createNew", obj.isCreateNew()); - json.put("deleteOnClose", obj.isDeleteOnClose()); - json.put("dsync", obj.isDsync()); if (obj.getPerms() != null) { json.put("perms", obj.getPerms()); } json.put("read", obj.isRead()); + json.put("write", obj.isWrite()); + json.put("create", obj.isCreate()); + json.put("createNew", obj.isCreateNew()); + json.put("deleteOnClose", obj.isDeleteOnClose()); + json.put("truncateExisting", obj.isTruncateExisting()); json.put("sparse", obj.isSparse()); json.put("sync", obj.isSync()); - json.put("truncateExisting", obj.isTruncateExisting()); - json.put("write", obj.isWrite()); + json.put("dsync", obj.isDsync()); + json.put("append", obj.isAppend()); } } diff --git a/src/main/generated/io/vertx/core/http/GoAwayConverter.java b/src/main/generated/io/vertx/core/http/GoAwayConverter.java index 47bf5c8fd91..5f9885f5ab8 100644 --- a/src/main/generated/io/vertx/core/http/GoAwayConverter.java +++ b/src/main/generated/io/vertx/core/http/GoAwayConverter.java @@ -20,11 +20,6 @@ public class GoAwayConverter { static void fromJson(Iterable> json, GoAway obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { - case "debugData": - if (member.getValue() instanceof String) { - obj.setDebugData(io.vertx.core.buffer.Buffer.buffer(BASE64_DECODER.decode((String)member.getValue()))); - } - break; case "errorCode": if (member.getValue() instanceof Number) { obj.setErrorCode(((Number)member.getValue()).longValue()); @@ -35,6 +30,11 @@ static void fromJson(Iterable> json, GoAway obj.setLastStreamId(((Number)member.getValue()).intValue()); } break; + case "debugData": + if (member.getValue() instanceof String) { + obj.setDebugData(io.vertx.core.buffer.Buffer.buffer(BASE64_DECODER.decode((String)member.getValue()))); + } + break; } } } @@ -44,10 +44,10 @@ static void toJson(GoAway obj, JsonObject json) { } static void toJson(GoAway obj, java.util.Map json) { + json.put("errorCode", obj.getErrorCode()); + json.put("lastStreamId", obj.getLastStreamId()); if (obj.getDebugData() != null) { json.put("debugData", BASE64_ENCODER.encodeToString(obj.getDebugData().getBytes())); } - json.put("errorCode", obj.getErrorCode()); - json.put("lastStreamId", obj.getLastStreamId()); } } diff --git a/src/main/generated/io/vertx/core/http/Http2SettingsConverter.java b/src/main/generated/io/vertx/core/http/Http2SettingsConverter.java index 0df44935cab..175e83e9490 100644 --- a/src/main/generated/io/vertx/core/http/Http2SettingsConverter.java +++ b/src/main/generated/io/vertx/core/http/Http2SettingsConverter.java @@ -25,9 +25,9 @@ static void fromJson(Iterable> json, Http2Se obj.setHeaderTableSize(((Number)member.getValue()).longValue()); } break; - case "initialWindowSize": - if (member.getValue() instanceof Number) { - obj.setInitialWindowSize(((Number)member.getValue()).intValue()); + case "pushEnabled": + if (member.getValue() instanceof Boolean) { + obj.setPushEnabled((Boolean)member.getValue()); } break; case "maxConcurrentStreams": @@ -35,6 +35,11 @@ static void fromJson(Iterable> json, Http2Se obj.setMaxConcurrentStreams(((Number)member.getValue()).longValue()); } break; + case "initialWindowSize": + if (member.getValue() instanceof Number) { + obj.setInitialWindowSize(((Number)member.getValue()).intValue()); + } + break; case "maxFrameSize": if (member.getValue() instanceof Number) { obj.setMaxFrameSize(((Number)member.getValue()).intValue()); @@ -45,11 +50,6 @@ static void fromJson(Iterable> json, Http2Se obj.setMaxHeaderListSize(((Number)member.getValue()).longValue()); } break; - case "pushEnabled": - if (member.getValue() instanceof Boolean) { - obj.setPushEnabled((Boolean)member.getValue()); - } - break; } } } @@ -60,10 +60,10 @@ static void toJson(Http2Settings obj, JsonObject json) { static void toJson(Http2Settings obj, java.util.Map json) { json.put("headerTableSize", obj.getHeaderTableSize()); - json.put("initialWindowSize", obj.getInitialWindowSize()); + json.put("pushEnabled", obj.isPushEnabled()); json.put("maxConcurrentStreams", obj.getMaxConcurrentStreams()); + json.put("initialWindowSize", obj.getInitialWindowSize()); json.put("maxFrameSize", obj.getMaxFrameSize()); json.put("maxHeaderListSize", obj.getMaxHeaderListSize()); - json.put("pushEnabled", obj.isPushEnabled()); } } diff --git a/src/main/generated/io/vertx/core/http/HttpClientOptionsConverter.java b/src/main/generated/io/vertx/core/http/HttpClientOptionsConverter.java index f5a5efe0ef3..41fb4b18091 100644 --- a/src/main/generated/io/vertx/core/http/HttpClientOptionsConverter.java +++ b/src/main/generated/io/vertx/core/http/HttpClientOptionsConverter.java @@ -20,44 +20,9 @@ public class HttpClientOptionsConverter { static void fromJson(Iterable> json, HttpClientOptions obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { - case "alpnVersions": - if (member.getValue() instanceof JsonArray) { - java.util.ArrayList list = new java.util.ArrayList<>(); - ((Iterable)member.getValue()).forEach( item -> { - if (item instanceof String) - list.add(io.vertx.core.http.HttpVersion.valueOf((String)item)); - }); - obj.setAlpnVersions(list); - } - break; - case "decoderInitialBufferSize": - if (member.getValue() instanceof Number) { - obj.setDecoderInitialBufferSize(((Number)member.getValue()).intValue()); - } - break; - case "defaultHost": - if (member.getValue() instanceof String) { - obj.setDefaultHost((String)member.getValue()); - } - break; - case "defaultPort": + case "http2MultiplexingLimit": if (member.getValue() instanceof Number) { - obj.setDefaultPort(((Number)member.getValue()).intValue()); - } - break; - case "forceSni": - if (member.getValue() instanceof Boolean) { - obj.setForceSni((Boolean)member.getValue()); - } - break; - case "http2ClearTextUpgrade": - if (member.getValue() instanceof Boolean) { - obj.setHttp2ClearTextUpgrade((Boolean)member.getValue()); - } - break; - case "http2ClearTextUpgradeWithPreflightRequest": - if (member.getValue() instanceof Boolean) { - obj.setHttp2ClearTextUpgradeWithPreflightRequest((Boolean)member.getValue()); + obj.setHttp2MultiplexingLimit(((Number)member.getValue()).intValue()); } break; case "http2ConnectionWindowSize": @@ -70,21 +35,6 @@ static void fromJson(Iterable> json, HttpCli obj.setHttp2KeepAliveTimeout(((Number)member.getValue()).intValue()); } break; - case "http2MaxPoolSize": - if (member.getValue() instanceof Number) { - obj.setHttp2MaxPoolSize(((Number)member.getValue()).intValue()); - } - break; - case "http2MultiplexingLimit": - if (member.getValue() instanceof Number) { - obj.setHttp2MultiplexingLimit(((Number)member.getValue()).intValue()); - } - break; - case "initialSettings": - if (member.getValue() instanceof JsonObject) { - obj.setInitialSettings(new io.vertx.core.http.Http2Settings((io.vertx.core.json.JsonObject)member.getValue())); - } - break; case "keepAlive": if (member.getValue() instanceof Boolean) { obj.setKeepAlive((Boolean)member.getValue()); @@ -95,136 +45,109 @@ static void fromJson(Iterable> json, HttpCli obj.setKeepAliveTimeout(((Number)member.getValue()).intValue()); } break; - case "maxChunkSize": - if (member.getValue() instanceof Number) { - obj.setMaxChunkSize(((Number)member.getValue()).intValue()); - } - break; - case "maxHeaderSize": - if (member.getValue() instanceof Number) { - obj.setMaxHeaderSize(((Number)member.getValue()).intValue()); - } - break; - case "maxInitialLineLength": - if (member.getValue() instanceof Number) { - obj.setMaxInitialLineLength(((Number)member.getValue()).intValue()); - } - break; - case "maxPoolSize": - if (member.getValue() instanceof Number) { - obj.setMaxPoolSize(((Number)member.getValue()).intValue()); + case "pipelining": + if (member.getValue() instanceof Boolean) { + obj.setPipelining((Boolean)member.getValue()); } break; - case "maxRedirects": + case "pipeliningLimit": if (member.getValue() instanceof Number) { - obj.setMaxRedirects(((Number)member.getValue()).intValue()); + obj.setPipeliningLimit(((Number)member.getValue()).intValue()); } break; - case "maxWaitQueueSize": - if (member.getValue() instanceof Number) { - obj.setMaxWaitQueueSize(((Number)member.getValue()).intValue()); + case "verifyHost": + if (member.getValue() instanceof Boolean) { + obj.setVerifyHost((Boolean)member.getValue()); } break; - case "maxWebSocketFrameSize": - if (member.getValue() instanceof Number) { - obj.setMaxWebSocketFrameSize(((Number)member.getValue()).intValue()); + case "decompressionSupported": + if (member.getValue() instanceof Boolean) { + obj.setDecompressionSupported((Boolean)member.getValue()); } break; - case "maxWebSocketMessageSize": - if (member.getValue() instanceof Number) { - obj.setMaxWebSocketMessageSize(((Number)member.getValue()).intValue()); + case "defaultHost": + if (member.getValue() instanceof String) { + obj.setDefaultHost((String)member.getValue()); } break; - case "maxWebSockets": + case "defaultPort": if (member.getValue() instanceof Number) { - obj.setMaxWebSockets(((Number)member.getValue()).intValue()); + obj.setDefaultPort(((Number)member.getValue()).intValue()); } break; - case "name": + case "protocolVersion": if (member.getValue() instanceof String) { - obj.setName((String)member.getValue()); - } - break; - case "pipelining": - if (member.getValue() instanceof Boolean) { - obj.setPipelining((Boolean)member.getValue()); + obj.setProtocolVersion(io.vertx.core.http.HttpVersion.valueOf((String)member.getValue())); } break; - case "pipeliningLimit": + case "maxChunkSize": if (member.getValue() instanceof Number) { - obj.setPipeliningLimit(((Number)member.getValue()).intValue()); + obj.setMaxChunkSize(((Number)member.getValue()).intValue()); } break; - case "poolCleanerPeriod": + case "maxInitialLineLength": if (member.getValue() instanceof Number) { - obj.setPoolCleanerPeriod(((Number)member.getValue()).intValue()); + obj.setMaxInitialLineLength(((Number)member.getValue()).intValue()); } break; - case "poolEventLoopSize": + case "maxHeaderSize": if (member.getValue() instanceof Number) { - obj.setPoolEventLoopSize(((Number)member.getValue()).intValue()); - } - break; - case "protocolVersion": - if (member.getValue() instanceof String) { - obj.setProtocolVersion(io.vertx.core.http.HttpVersion.valueOf((String)member.getValue())); - } - break; - case "sendUnmaskedFrames": - if (member.getValue() instanceof Boolean) { - obj.setSendUnmaskedFrames((Boolean)member.getValue()); + obj.setMaxHeaderSize(((Number)member.getValue()).intValue()); } break; - case "shared": - if (member.getValue() instanceof Boolean) { - obj.setShared((Boolean)member.getValue()); + case "initialSettings": + if (member.getValue() instanceof JsonObject) { + obj.setInitialSettings(new io.vertx.core.http.Http2Settings((io.vertx.core.json.JsonObject)member.getValue())); } break; - case "tracingPolicy": - if (member.getValue() instanceof String) { - obj.setTracingPolicy(io.vertx.core.tracing.TracingPolicy.valueOf((String)member.getValue())); + case "alpnVersions": + if (member.getValue() instanceof JsonArray) { + java.util.ArrayList list = new java.util.ArrayList<>(); + ((Iterable)member.getValue()).forEach( item -> { + if (item instanceof String) + list.add(io.vertx.core.http.HttpVersion.valueOf((String)item)); + }); + obj.setAlpnVersions(list); } break; - case "tryUseCompression": + case "http2ClearTextUpgrade": if (member.getValue() instanceof Boolean) { - obj.setTryUseCompression((Boolean)member.getValue()); + obj.setHttp2ClearTextUpgrade((Boolean)member.getValue()); } break; - case "tryUsePerFrameWebSocketCompression": + case "http2ClearTextUpgradeWithPreflightRequest": if (member.getValue() instanceof Boolean) { - obj.setTryUsePerFrameWebSocketCompression((Boolean)member.getValue()); + obj.setHttp2ClearTextUpgradeWithPreflightRequest((Boolean)member.getValue()); } break; - case "tryUsePerMessageWebSocketCompression": - if (member.getValue() instanceof Boolean) { - obj.setTryUsePerMessageWebSocketCompression((Boolean)member.getValue()); + case "maxRedirects": + if (member.getValue() instanceof Number) { + obj.setMaxRedirects(((Number)member.getValue()).intValue()); } break; - case "tryWebSocketDeflateFrameCompression": - break; - case "verifyHost": + case "forceSni": if (member.getValue() instanceof Boolean) { - obj.setVerifyHost((Boolean)member.getValue()); + obj.setForceSni((Boolean)member.getValue()); } break; - case "webSocketClosingTimeout": + case "decoderInitialBufferSize": if (member.getValue() instanceof Number) { - obj.setWebSocketClosingTimeout(((Number)member.getValue()).intValue()); + obj.setDecoderInitialBufferSize(((Number)member.getValue()).intValue()); } break; - case "webSocketCompressionAllowClientNoContext": - if (member.getValue() instanceof Boolean) { - obj.setWebSocketCompressionAllowClientNoContext((Boolean)member.getValue()); + case "tracingPolicy": + if (member.getValue() instanceof String) { + obj.setTracingPolicy(io.vertx.core.tracing.TracingPolicy.valueOf((String)member.getValue())); } break; - case "webSocketCompressionLevel": - if (member.getValue() instanceof Number) { - obj.setWebSocketCompressionLevel(((Number)member.getValue()).intValue()); + case "shared": + if (member.getValue() instanceof Boolean) { + obj.setShared((Boolean)member.getValue()); } break; - case "webSocketCompressionRequestServerNoContext": - if (member.getValue() instanceof Boolean) { - obj.setWebSocketCompressionRequestServerNoContext((Boolean)member.getValue()); + case "name": + if (member.getValue() instanceof String) { + obj.setName((String)member.getValue()); } break; } @@ -236,59 +159,44 @@ static void toJson(HttpClientOptions obj, JsonObject json) { } static void toJson(HttpClientOptions obj, java.util.Map json) { - if (obj.getAlpnVersions() != null) { - JsonArray array = new JsonArray(); - obj.getAlpnVersions().forEach(item -> array.add(item.name())); - json.put("alpnVersions", array); - } - json.put("decoderInitialBufferSize", obj.getDecoderInitialBufferSize()); - if (obj.getDefaultHost() != null) { - json.put("defaultHost", obj.getDefaultHost()); - } - json.put("defaultPort", obj.getDefaultPort()); - json.put("forceSni", obj.isForceSni()); - json.put("http2ClearTextUpgrade", obj.isHttp2ClearTextUpgrade()); - json.put("http2ClearTextUpgradeWithPreflightRequest", obj.isHttp2ClearTextUpgradeWithPreflightRequest()); + json.put("http2MultiplexingLimit", obj.getHttp2MultiplexingLimit()); json.put("http2ConnectionWindowSize", obj.getHttp2ConnectionWindowSize()); json.put("http2KeepAliveTimeout", obj.getHttp2KeepAliveTimeout()); - json.put("http2MaxPoolSize", obj.getHttp2MaxPoolSize()); - json.put("http2MultiplexingLimit", obj.getHttp2MultiplexingLimit()); - if (obj.getInitialSettings() != null) { - json.put("initialSettings", obj.getInitialSettings().toJson()); - } json.put("keepAlive", obj.isKeepAlive()); json.put("keepAliveTimeout", obj.getKeepAliveTimeout()); - json.put("maxChunkSize", obj.getMaxChunkSize()); - json.put("maxHeaderSize", obj.getMaxHeaderSize()); - json.put("maxInitialLineLength", obj.getMaxInitialLineLength()); - json.put("maxPoolSize", obj.getMaxPoolSize()); - json.put("maxRedirects", obj.getMaxRedirects()); - json.put("maxWaitQueueSize", obj.getMaxWaitQueueSize()); - json.put("maxWebSocketFrameSize", obj.getMaxWebSocketFrameSize()); - json.put("maxWebSocketMessageSize", obj.getMaxWebSocketMessageSize()); - json.put("maxWebSockets", obj.getMaxWebSockets()); - if (obj.getName() != null) { - json.put("name", obj.getName()); - } json.put("pipelining", obj.isPipelining()); json.put("pipeliningLimit", obj.getPipeliningLimit()); - json.put("poolCleanerPeriod", obj.getPoolCleanerPeriod()); - json.put("poolEventLoopSize", obj.getPoolEventLoopSize()); + json.put("verifyHost", obj.isVerifyHost()); + json.put("decompressionSupported", obj.isDecompressionSupported()); + if (obj.getDefaultHost() != null) { + json.put("defaultHost", obj.getDefaultHost()); + } + json.put("defaultPort", obj.getDefaultPort()); if (obj.getProtocolVersion() != null) { json.put("protocolVersion", obj.getProtocolVersion().name()); } - json.put("sendUnmaskedFrames", obj.isSendUnmaskedFrames()); - json.put("shared", obj.isShared()); + json.put("maxChunkSize", obj.getMaxChunkSize()); + json.put("maxInitialLineLength", obj.getMaxInitialLineLength()); + json.put("maxHeaderSize", obj.getMaxHeaderSize()); + if (obj.getInitialSettings() != null) { + json.put("initialSettings", obj.getInitialSettings().toJson()); + } + if (obj.getAlpnVersions() != null) { + JsonArray array = new JsonArray(); + obj.getAlpnVersions().forEach(item -> array.add(item.name())); + json.put("alpnVersions", array); + } + json.put("http2ClearTextUpgrade", obj.isHttp2ClearTextUpgrade()); + json.put("http2ClearTextUpgradeWithPreflightRequest", obj.isHttp2ClearTextUpgradeWithPreflightRequest()); + json.put("maxRedirects", obj.getMaxRedirects()); + json.put("forceSni", obj.isForceSni()); + json.put("decoderInitialBufferSize", obj.getDecoderInitialBufferSize()); if (obj.getTracingPolicy() != null) { json.put("tracingPolicy", obj.getTracingPolicy().name()); } - json.put("tryUseCompression", obj.isTryUseCompression()); - json.put("tryUsePerMessageWebSocketCompression", obj.getTryUsePerMessageWebSocketCompression()); - json.put("tryWebSocketDeflateFrameCompression", obj.getTryWebSocketDeflateFrameCompression()); - json.put("verifyHost", obj.isVerifyHost()); - json.put("webSocketClosingTimeout", obj.getWebSocketClosingTimeout()); - json.put("webSocketCompressionAllowClientNoContext", obj.getWebSocketCompressionAllowClientNoContext()); - json.put("webSocketCompressionLevel", obj.getWebSocketCompressionLevel()); - json.put("webSocketCompressionRequestServerNoContext", obj.getWebSocketCompressionRequestServerNoContext()); + json.put("shared", obj.isShared()); + if (obj.getName() != null) { + json.put("name", obj.getName()); + } } } diff --git a/src/main/generated/io/vertx/core/http/HttpServerOptionsConverter.java b/src/main/generated/io/vertx/core/http/HttpServerOptionsConverter.java index 866f1fce1ae..2eac4073586 100644 --- a/src/main/generated/io/vertx/core/http/HttpServerOptionsConverter.java +++ b/src/main/generated/io/vertx/core/http/HttpServerOptionsConverter.java @@ -20,19 +20,9 @@ public class HttpServerOptionsConverter { static void fromJson(Iterable> json, HttpServerOptions obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { - case "acceptUnmaskedFrames": + case "compressionSupported": if (member.getValue() instanceof Boolean) { - obj.setAcceptUnmaskedFrames((Boolean)member.getValue()); - } - break; - case "alpnVersions": - if (member.getValue() instanceof JsonArray) { - java.util.ArrayList list = new java.util.ArrayList<>(); - ((Iterable)member.getValue()).forEach( item -> { - if (item instanceof String) - list.add(io.vertx.core.http.HttpVersion.valueOf((String)item)); - }); - obj.setAlpnVersions(list); + obj.setCompressionSupported((Boolean)member.getValue()); } break; case "compressionLevel": @@ -40,19 +30,29 @@ static void fromJson(Iterable> json, HttpSer obj.setCompressionLevel(((Number)member.getValue()).intValue()); } break; - case "compressionSupported": + case "acceptUnmaskedFrames": if (member.getValue() instanceof Boolean) { - obj.setCompressionSupported((Boolean)member.getValue()); + obj.setAcceptUnmaskedFrames((Boolean)member.getValue()); } break; - case "decoderInitialBufferSize": + case "maxWebSocketFrameSize": if (member.getValue() instanceof Number) { - obj.setDecoderInitialBufferSize(((Number)member.getValue()).intValue()); + obj.setMaxWebSocketFrameSize(((Number)member.getValue()).intValue()); } break; - case "decompressionSupported": - if (member.getValue() instanceof Boolean) { - obj.setDecompressionSupported((Boolean)member.getValue()); + case "maxWebSocketMessageSize": + if (member.getValue() instanceof Number) { + obj.setMaxWebSocketMessageSize(((Number)member.getValue()).intValue()); + } + break; + case "webSocketSubProtocols": + if (member.getValue() instanceof JsonArray) { + java.util.ArrayList list = new java.util.ArrayList<>(); + ((Iterable)member.getValue()).forEach( item -> { + if (item instanceof String) + list.add((String)item); + }); + obj.setWebSocketSubProtocols(list); } break; case "handle100ContinueAutomatically": @@ -60,19 +60,19 @@ static void fromJson(Iterable> json, HttpSer obj.setHandle100ContinueAutomatically((Boolean)member.getValue()); } break; - case "http2ConnectionWindowSize": + case "maxChunkSize": if (member.getValue() instanceof Number) { - obj.setHttp2ConnectionWindowSize(((Number)member.getValue()).intValue()); + obj.setMaxChunkSize(((Number)member.getValue()).intValue()); } break; - case "initialSettings": - if (member.getValue() instanceof JsonObject) { - obj.setInitialSettings(new io.vertx.core.http.Http2Settings((io.vertx.core.json.JsonObject)member.getValue())); + case "maxInitialLineLength": + if (member.getValue() instanceof Number) { + obj.setMaxInitialLineLength(((Number)member.getValue()).intValue()); } break; - case "maxChunkSize": + case "maxHeaderSize": if (member.getValue() instanceof Number) { - obj.setMaxChunkSize(((Number)member.getValue()).intValue()); + obj.setMaxHeaderSize(((Number)member.getValue()).intValue()); } break; case "maxFormAttributeSize": @@ -80,24 +80,39 @@ static void fromJson(Iterable> json, HttpSer obj.setMaxFormAttributeSize(((Number)member.getValue()).intValue()); } break; - case "maxHeaderSize": - if (member.getValue() instanceof Number) { - obj.setMaxHeaderSize(((Number)member.getValue()).intValue()); + case "initialSettings": + if (member.getValue() instanceof JsonObject) { + obj.setInitialSettings(new io.vertx.core.http.Http2Settings((io.vertx.core.json.JsonObject)member.getValue())); } break; - case "maxInitialLineLength": - if (member.getValue() instanceof Number) { - obj.setMaxInitialLineLength(((Number)member.getValue()).intValue()); + case "alpnVersions": + if (member.getValue() instanceof JsonArray) { + java.util.ArrayList list = new java.util.ArrayList<>(); + ((Iterable)member.getValue()).forEach( item -> { + if (item instanceof String) + list.add(io.vertx.core.http.HttpVersion.valueOf((String)item)); + }); + obj.setAlpnVersions(list); } break; - case "maxWebSocketFrameSize": + case "http2ClearTextEnabled": + if (member.getValue() instanceof Boolean) { + obj.setHttp2ClearTextEnabled((Boolean)member.getValue()); + } + break; + case "http2ConnectionWindowSize": if (member.getValue() instanceof Number) { - obj.setMaxWebSocketFrameSize(((Number)member.getValue()).intValue()); + obj.setHttp2ConnectionWindowSize(((Number)member.getValue()).intValue()); } break; - case "maxWebSocketMessageSize": + case "decompressionSupported": + if (member.getValue() instanceof Boolean) { + obj.setDecompressionSupported((Boolean)member.getValue()); + } + break; + case "decoderInitialBufferSize": if (member.getValue() instanceof Number) { - obj.setMaxWebSocketMessageSize(((Number)member.getValue()).intValue()); + obj.setDecoderInitialBufferSize(((Number)member.getValue()).intValue()); } break; case "perFrameWebSocketCompressionSupported": @@ -110,14 +125,9 @@ static void fromJson(Iterable> json, HttpSer obj.setPerMessageWebSocketCompressionSupported((Boolean)member.getValue()); } break; - case "registerWebSocketWriteHandlers": - if (member.getValue() instanceof Boolean) { - obj.setRegisterWebSocketWriteHandlers((Boolean)member.getValue()); - } - break; - case "tracingPolicy": - if (member.getValue() instanceof String) { - obj.setTracingPolicy(io.vertx.core.tracing.TracingPolicy.valueOf((String)member.getValue())); + case "webSocketCompressionLevel": + if (member.getValue() instanceof Number) { + obj.setWebSocketCompressionLevel(((Number)member.getValue()).intValue()); } break; case "webSocketAllowServerNoContext": @@ -125,29 +135,39 @@ static void fromJson(Iterable> json, HttpSer obj.setWebSocketAllowServerNoContext((Boolean)member.getValue()); } break; + case "webSocketPreferredClientNoContext": + if (member.getValue() instanceof Boolean) { + obj.setWebSocketPreferredClientNoContext((Boolean)member.getValue()); + } + break; case "webSocketClosingTimeout": if (member.getValue() instanceof Number) { obj.setWebSocketClosingTimeout(((Number)member.getValue()).intValue()); } break; - case "webSocketCompressionLevel": - if (member.getValue() instanceof Number) { - obj.setWebSocketCompressionLevel(((Number)member.getValue()).intValue()); + case "tracingPolicy": + if (member.getValue() instanceof String) { + obj.setTracingPolicy(io.vertx.core.tracing.TracingPolicy.valueOf((String)member.getValue())); } break; - case "webSocketPreferredClientNoContext": + case "registerWebSocketWriteHandlers": if (member.getValue() instanceof Boolean) { - obj.setWebSocketPreferredClientNoContext((Boolean)member.getValue()); + obj.setRegisterWebSocketWriteHandlers((Boolean)member.getValue()); } break; - case "webSocketSubProtocols": - if (member.getValue() instanceof JsonArray) { - java.util.ArrayList list = new java.util.ArrayList<>(); - ((Iterable)member.getValue()).forEach( item -> { - if (item instanceof String) - list.add((String)item); - }); - obj.setWebSocketSubProtocols(list); + case "http2RstFloodMaxRstFramePerWindow": + if (member.getValue() instanceof Number) { + obj.setHttp2RstFloodMaxRstFramePerWindow(((Number)member.getValue()).intValue()); + } + break; + case "http2RstFloodWindowDuration": + if (member.getValue() instanceof Number) { + obj.setHttp2RstFloodWindowDuration(((Number)member.getValue()).intValue()); + } + break; + case "http2RstFloodWindowDurationTimeUnit": + if (member.getValue() instanceof String) { + obj.setHttp2RstFloodWindowDurationTimeUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); } break; } @@ -159,41 +179,47 @@ static void toJson(HttpServerOptions obj, JsonObject json) { } static void toJson(HttpServerOptions obj, java.util.Map json) { + json.put("compressionSupported", obj.isCompressionSupported()); + json.put("compressionLevel", obj.getCompressionLevel()); json.put("acceptUnmaskedFrames", obj.isAcceptUnmaskedFrames()); - if (obj.getAlpnVersions() != null) { + json.put("maxWebSocketFrameSize", obj.getMaxWebSocketFrameSize()); + json.put("maxWebSocketMessageSize", obj.getMaxWebSocketMessageSize()); + if (obj.getWebSocketSubProtocols() != null) { JsonArray array = new JsonArray(); - obj.getAlpnVersions().forEach(item -> array.add(item.name())); - json.put("alpnVersions", array); + obj.getWebSocketSubProtocols().forEach(item -> array.add(item)); + json.put("webSocketSubProtocols", array); } - json.put("compressionLevel", obj.getCompressionLevel()); - json.put("compressionSupported", obj.isCompressionSupported()); - json.put("decoderInitialBufferSize", obj.getDecoderInitialBufferSize()); - json.put("decompressionSupported", obj.isDecompressionSupported()); json.put("handle100ContinueAutomatically", obj.isHandle100ContinueAutomatically()); - json.put("http2ConnectionWindowSize", obj.getHttp2ConnectionWindowSize()); + json.put("maxChunkSize", obj.getMaxChunkSize()); + json.put("maxInitialLineLength", obj.getMaxInitialLineLength()); + json.put("maxHeaderSize", obj.getMaxHeaderSize()); + json.put("maxFormAttributeSize", obj.getMaxFormAttributeSize()); if (obj.getInitialSettings() != null) { json.put("initialSettings", obj.getInitialSettings().toJson()); } - json.put("maxChunkSize", obj.getMaxChunkSize()); - json.put("maxFormAttributeSize", obj.getMaxFormAttributeSize()); - json.put("maxHeaderSize", obj.getMaxHeaderSize()); - json.put("maxInitialLineLength", obj.getMaxInitialLineLength()); - json.put("maxWebSocketFrameSize", obj.getMaxWebSocketFrameSize()); - json.put("maxWebSocketMessageSize", obj.getMaxWebSocketMessageSize()); + if (obj.getAlpnVersions() != null) { + JsonArray array = new JsonArray(); + obj.getAlpnVersions().forEach(item -> array.add(item.name())); + json.put("alpnVersions", array); + } + json.put("http2ClearTextEnabled", obj.isHttp2ClearTextEnabled()); + json.put("http2ConnectionWindowSize", obj.getHttp2ConnectionWindowSize()); + json.put("decompressionSupported", obj.isDecompressionSupported()); + json.put("decoderInitialBufferSize", obj.getDecoderInitialBufferSize()); json.put("perFrameWebSocketCompressionSupported", obj.getPerFrameWebSocketCompressionSupported()); json.put("perMessageWebSocketCompressionSupported", obj.getPerMessageWebSocketCompressionSupported()); - json.put("registerWebSocketWriteHandlers", obj.isRegisterWebSocketWriteHandlers()); + json.put("webSocketCompressionLevel", obj.getWebSocketCompressionLevel()); + json.put("webSocketAllowServerNoContext", obj.getWebSocketAllowServerNoContext()); + json.put("webSocketPreferredClientNoContext", obj.getWebSocketPreferredClientNoContext()); + json.put("webSocketClosingTimeout", obj.getWebSocketClosingTimeout()); if (obj.getTracingPolicy() != null) { json.put("tracingPolicy", obj.getTracingPolicy().name()); } - json.put("webSocketAllowServerNoContext", obj.getWebSocketAllowServerNoContext()); - json.put("webSocketClosingTimeout", obj.getWebSocketClosingTimeout()); - json.put("webSocketCompressionLevel", obj.getWebSocketCompressionLevel()); - json.put("webSocketPreferredClientNoContext", obj.getWebSocketPreferredClientNoContext()); - if (obj.getWebSocketSubProtocols() != null) { - JsonArray array = new JsonArray(); - obj.getWebSocketSubProtocols().forEach(item -> array.add(item)); - json.put("webSocketSubProtocols", array); + json.put("registerWebSocketWriteHandlers", obj.isRegisterWebSocketWriteHandlers()); + json.put("http2RstFloodMaxRstFramePerWindow", obj.getHttp2RstFloodMaxRstFramePerWindow()); + json.put("http2RstFloodWindowDuration", obj.getHttp2RstFloodWindowDuration()); + if (obj.getHttp2RstFloodWindowDurationTimeUnit() != null) { + json.put("http2RstFloodWindowDurationTimeUnit", obj.getHttp2RstFloodWindowDurationTimeUnit().name()); } } } diff --git a/src/main/generated/io/vertx/core/http/PoolOptionsConverter.java b/src/main/generated/io/vertx/core/http/PoolOptionsConverter.java new file mode 100644 index 00000000000..af29ea4edd0 --- /dev/null +++ b/src/main/generated/io/vertx/core/http/PoolOptionsConverter.java @@ -0,0 +1,63 @@ +package io.vertx.core.http; + +import io.vertx.core.json.JsonObject; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.impl.JsonUtil; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.Base64; + +/** + * Converter and mapper for {@link io.vertx.core.http.PoolOptions}. + * NOTE: This class has been automatically generated from the {@link io.vertx.core.http.PoolOptions} original class using Vert.x codegen. + */ +public class PoolOptionsConverter { + + + private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER; + private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER; + + static void fromJson(Iterable> json, PoolOptions obj) { + for (java.util.Map.Entry member : json) { + switch (member.getKey()) { + case "http1MaxSize": + if (member.getValue() instanceof Number) { + obj.setHttp1MaxSize(((Number)member.getValue()).intValue()); + } + break; + case "http2MaxSize": + if (member.getValue() instanceof Number) { + obj.setHttp2MaxSize(((Number)member.getValue()).intValue()); + } + break; + case "cleanerPeriod": + if (member.getValue() instanceof Number) { + obj.setCleanerPeriod(((Number)member.getValue()).intValue()); + } + break; + case "eventLoopSize": + if (member.getValue() instanceof Number) { + obj.setEventLoopSize(((Number)member.getValue()).intValue()); + } + break; + case "maxWaitQueueSize": + if (member.getValue() instanceof Number) { + obj.setMaxWaitQueueSize(((Number)member.getValue()).intValue()); + } + break; + } + } + } + + static void toJson(PoolOptions obj, JsonObject json) { + toJson(obj, json.getMap()); + } + + static void toJson(PoolOptions obj, java.util.Map json) { + json.put("http1MaxSize", obj.getHttp1MaxSize()); + json.put("http2MaxSize", obj.getHttp2MaxSize()); + json.put("cleanerPeriod", obj.getCleanerPeriod()); + json.put("eventLoopSize", obj.getEventLoopSize()); + json.put("maxWaitQueueSize", obj.getMaxWaitQueueSize()); + } +} diff --git a/src/main/generated/io/vertx/core/http/RequestOptionsConverter.java b/src/main/generated/io/vertx/core/http/RequestOptionsConverter.java index 1768ab026d7..680d317d4fb 100644 --- a/src/main/generated/io/vertx/core/http/RequestOptionsConverter.java +++ b/src/main/generated/io/vertx/core/http/RequestOptionsConverter.java @@ -17,17 +17,12 @@ public class RequestOptionsConverter { private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER; private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER; - public static void fromJson(Iterable> json, RequestOptions obj) { + static void fromJson(Iterable> json, RequestOptions obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { - case "absoluteURI": - if (member.getValue() instanceof String) { - obj.setAbsoluteURI((String)member.getValue()); - } - break; - case "followRedirects": - if (member.getValue() instanceof Boolean) { - obj.setFollowRedirects((Boolean)member.getValue()); + case "proxyOptions": + if (member.getValue() instanceof JsonObject) { + obj.setProxyOptions(new io.vertx.core.net.ProxyOptions((io.vertx.core.json.JsonObject)member.getValue())); } break; case "host": @@ -40,14 +35,24 @@ public static void fromJson(Iterable> json, obj.setPort(((Number)member.getValue()).intValue()); } break; - case "proxyOptions": + case "ssl": + if (member.getValue() instanceof Boolean) { + obj.setSsl((Boolean)member.getValue()); + } + break; + case "sslOptions": if (member.getValue() instanceof JsonObject) { - obj.setProxyOptions(new io.vertx.core.net.ProxyOptions((io.vertx.core.json.JsonObject)member.getValue())); + obj.setSslOptions(new io.vertx.core.net.ClientSSLOptions((io.vertx.core.json.JsonObject)member.getValue())); } break; - case "ssl": + case "uri": + if (member.getValue() instanceof String) { + obj.setURI((String)member.getValue()); + } + break; + case "followRedirects": if (member.getValue() instanceof Boolean) { - obj.setSsl((Boolean)member.getValue()); + obj.setFollowRedirects((Boolean)member.getValue()); } break; case "timeout": @@ -55,27 +60,37 @@ public static void fromJson(Iterable> json, obj.setTimeout(((Number)member.getValue()).longValue()); } break; - case "traceOperation": + case "connectTimeout": + if (member.getValue() instanceof Number) { + obj.setConnectTimeout(((Number)member.getValue()).longValue()); + } + break; + case "idleTimeout": + if (member.getValue() instanceof Number) { + obj.setIdleTimeout(((Number)member.getValue()).longValue()); + } + break; + case "absoluteURI": if (member.getValue() instanceof String) { - obj.setTraceOperation((String)member.getValue()); + obj.setAbsoluteURI((String)member.getValue()); } break; - case "uri": + case "traceOperation": if (member.getValue() instanceof String) { - obj.setURI((String)member.getValue()); + obj.setTraceOperation((String)member.getValue()); } break; } } } - public static void toJson(RequestOptions obj, JsonObject json) { + static void toJson(RequestOptions obj, JsonObject json) { toJson(obj, json.getMap()); } - public static void toJson(RequestOptions obj, java.util.Map json) { - if (obj.getFollowRedirects() != null) { - json.put("followRedirects", obj.getFollowRedirects()); + static void toJson(RequestOptions obj, java.util.Map json) { + if (obj.getProxyOptions() != null) { + json.put("proxyOptions", obj.getProxyOptions().toJson()); } if (obj.getHost() != null) { json.put("host", obj.getHost()); @@ -83,18 +98,23 @@ public static void toJson(RequestOptions obj, java.util.Map json if (obj.getPort() != null) { json.put("port", obj.getPort()); } - if (obj.getProxyOptions() != null) { - json.put("proxyOptions", obj.getProxyOptions().toJson()); - } if (obj.isSsl() != null) { json.put("ssl", obj.isSsl()); } - json.put("timeout", obj.getTimeout()); - if (obj.getTraceOperation() != null) { - json.put("traceOperation", obj.getTraceOperation()); + if (obj.getSslOptions() != null) { + json.put("sslOptions", obj.getSslOptions().toJson()); } if (obj.getURI() != null) { json.put("uri", obj.getURI()); } + if (obj.getFollowRedirects() != null) { + json.put("followRedirects", obj.getFollowRedirects()); + } + json.put("timeout", obj.getTimeout()); + json.put("connectTimeout", obj.getConnectTimeout()); + json.put("idleTimeout", obj.getIdleTimeout()); + if (obj.getTraceOperation() != null) { + json.put("traceOperation", obj.getTraceOperation()); + } } } diff --git a/src/main/generated/io/vertx/core/http/WebSocketClientOptionsConverter.java b/src/main/generated/io/vertx/core/http/WebSocketClientOptionsConverter.java new file mode 100644 index 00000000000..c295bd6318a --- /dev/null +++ b/src/main/generated/io/vertx/core/http/WebSocketClientOptionsConverter.java @@ -0,0 +1,127 @@ +package io.vertx.core.http; + +import io.vertx.core.json.JsonObject; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.impl.JsonUtil; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.Base64; + +/** + * Converter and mapper for {@link io.vertx.core.http.WebSocketClientOptions}. + * NOTE: This class has been automatically generated from the {@link io.vertx.core.http.WebSocketClientOptions} original class using Vert.x codegen. + */ +public class WebSocketClientOptionsConverter { + + + private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER; + private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER; + + public static void fromJson(Iterable> json, WebSocketClientOptions obj) { + for (java.util.Map.Entry member : json) { + switch (member.getKey()) { + case "defaultHost": + if (member.getValue() instanceof String) { + obj.setDefaultHost((String)member.getValue()); + } + break; + case "defaultPort": + if (member.getValue() instanceof Number) { + obj.setDefaultPort(((Number)member.getValue()).intValue()); + } + break; + case "verifyHost": + if (member.getValue() instanceof Boolean) { + obj.setVerifyHost((Boolean)member.getValue()); + } + break; + case "sendUnmaskedFrames": + if (member.getValue() instanceof Boolean) { + obj.setSendUnmaskedFrames((Boolean)member.getValue()); + } + break; + case "maxFrameSize": + if (member.getValue() instanceof Number) { + obj.setMaxFrameSize(((Number)member.getValue()).intValue()); + } + break; + case "maxMessageSize": + if (member.getValue() instanceof Number) { + obj.setMaxMessageSize(((Number)member.getValue()).intValue()); + } + break; + case "maxConnections": + if (member.getValue() instanceof Number) { + obj.setMaxConnections(((Number)member.getValue()).intValue()); + } + break; + case "tryUsePerFrameCompression": + if (member.getValue() instanceof Boolean) { + obj.setTryUsePerFrameCompression((Boolean)member.getValue()); + } + break; + case "tryUsePerMessageCompression": + if (member.getValue() instanceof Boolean) { + obj.setTryUsePerMessageCompression((Boolean)member.getValue()); + } + break; + case "compressionLevel": + if (member.getValue() instanceof Number) { + obj.setCompressionLevel(((Number)member.getValue()).intValue()); + } + break; + case "compressionAllowClientNoContext": + if (member.getValue() instanceof Boolean) { + obj.setCompressionAllowClientNoContext((Boolean)member.getValue()); + } + break; + case "compressionRequestServerNoContext": + if (member.getValue() instanceof Boolean) { + obj.setCompressionRequestServerNoContext((Boolean)member.getValue()); + } + break; + case "closingTimeout": + if (member.getValue() instanceof Number) { + obj.setClosingTimeout(((Number)member.getValue()).intValue()); + } + break; + case "shared": + if (member.getValue() instanceof Boolean) { + obj.setShared((Boolean)member.getValue()); + } + break; + case "name": + if (member.getValue() instanceof String) { + obj.setName((String)member.getValue()); + } + break; + } + } + } + + public static void toJson(WebSocketClientOptions obj, JsonObject json) { + toJson(obj, json.getMap()); + } + + public static void toJson(WebSocketClientOptions obj, java.util.Map json) { + if (obj.getDefaultHost() != null) { + json.put("defaultHost", obj.getDefaultHost()); + } + json.put("defaultPort", obj.getDefaultPort()); + json.put("verifyHost", obj.isVerifyHost()); + json.put("sendUnmaskedFrames", obj.isSendUnmaskedFrames()); + json.put("maxFrameSize", obj.getMaxFrameSize()); + json.put("maxMessageSize", obj.getMaxMessageSize()); + json.put("maxConnections", obj.getMaxConnections()); + json.put("tryUsePerFrameCompression", obj.getTryUsePerFrameCompression()); + json.put("tryUsePerMessageCompression", obj.getTryUsePerMessageCompression()); + json.put("compressionLevel", obj.getCompressionLevel()); + json.put("compressionAllowClientNoContext", obj.getCompressionAllowClientNoContext()); + json.put("compressionRequestServerNoContext", obj.getCompressionRequestServerNoContext()); + json.put("closingTimeout", obj.getClosingTimeout()); + json.put("shared", obj.isShared()); + if (obj.getName() != null) { + json.put("name", obj.getName()); + } + } +} diff --git a/src/main/generated/io/vertx/core/http/WebSocketConnectOptionsConverter.java b/src/main/generated/io/vertx/core/http/WebSocketConnectOptionsConverter.java index ccf88383f0e..280912dc00b 100644 --- a/src/main/generated/io/vertx/core/http/WebSocketConnectOptionsConverter.java +++ b/src/main/generated/io/vertx/core/http/WebSocketConnectOptionsConverter.java @@ -17,17 +17,12 @@ public class WebSocketConnectOptionsConverter { private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER; private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER; - public static void fromJson(Iterable> json, WebSocketConnectOptions obj) { + static void fromJson(Iterable> json, WebSocketConnectOptions obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { - case "allowOriginHeader": - if (member.getValue() instanceof Boolean) { - obj.setAllowOriginHeader((Boolean)member.getValue()); - } - break; - case "registerWriteHandlers": - if (member.getValue() instanceof Boolean) { - obj.setRegisterWriteHandlers((Boolean)member.getValue()); + case "version": + if (member.getValue() instanceof String) { + obj.setVersion(io.vertx.core.http.WebsocketVersion.valueOf((String)member.getValue())); } break; case "subProtocols": @@ -40,29 +35,34 @@ public static void fromJson(Iterable> json, obj.setSubProtocols(list); } break; - case "version": - if (member.getValue() instanceof String) { - obj.setVersion(io.vertx.core.http.WebsocketVersion.valueOf((String)member.getValue())); + case "allowOriginHeader": + if (member.getValue() instanceof Boolean) { + obj.setAllowOriginHeader((Boolean)member.getValue()); + } + break; + case "registerWriteHandlers": + if (member.getValue() instanceof Boolean) { + obj.setRegisterWriteHandlers((Boolean)member.getValue()); } break; } } } - public static void toJson(WebSocketConnectOptions obj, JsonObject json) { + static void toJson(WebSocketConnectOptions obj, JsonObject json) { toJson(obj, json.getMap()); } - public static void toJson(WebSocketConnectOptions obj, java.util.Map json) { - json.put("allowOriginHeader", obj.getAllowOriginHeader()); - json.put("registerWriteHandlers", obj.isRegisterWriteHandlers()); + static void toJson(WebSocketConnectOptions obj, java.util.Map json) { + if (obj.getVersion() != null) { + json.put("version", obj.getVersion().name()); + } if (obj.getSubProtocols() != null) { JsonArray array = new JsonArray(); obj.getSubProtocols().forEach(item -> array.add(item)); json.put("subProtocols", array); } - if (obj.getVersion() != null) { - json.put("version", obj.getVersion().name()); - } + json.put("allowOriginHeader", obj.getAllowOriginHeader()); + json.put("registerWriteHandlers", obj.isRegisterWriteHandlers()); } } diff --git a/src/main/generated/io/vertx/core/net/ClientOptionsBaseConverter.java b/src/main/generated/io/vertx/core/net/ClientOptionsBaseConverter.java index a9fffd93a90..c20b879ec67 100644 --- a/src/main/generated/io/vertx/core/net/ClientOptionsBaseConverter.java +++ b/src/main/generated/io/vertx/core/net/ClientOptionsBaseConverter.java @@ -20,21 +20,26 @@ public class ClientOptionsBaseConverter { static void fromJson(Iterable> json, ClientOptionsBase obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { + case "trustAll": + if (member.getValue() instanceof Boolean) { + obj.setTrustAll((Boolean)member.getValue()); + } + break; case "connectTimeout": if (member.getValue() instanceof Number) { obj.setConnectTimeout(((Number)member.getValue()).intValue()); } break; - case "localAddress": - if (member.getValue() instanceof String) { - obj.setLocalAddress((String)member.getValue()); - } - break; case "metricsName": if (member.getValue() instanceof String) { obj.setMetricsName((String)member.getValue()); } break; + case "proxyOptions": + if (member.getValue() instanceof JsonObject) { + obj.setProxyOptions(new io.vertx.core.net.ProxyOptions((io.vertx.core.json.JsonObject)member.getValue())); + } + break; case "nonProxyHosts": if (member.getValue() instanceof JsonArray) { java.util.ArrayList list = new java.util.ArrayList<>(); @@ -45,14 +50,9 @@ static void fromJson(Iterable> json, ClientO obj.setNonProxyHosts(list); } break; - case "proxyOptions": - if (member.getValue() instanceof JsonObject) { - obj.setProxyOptions(new io.vertx.core.net.ProxyOptions((io.vertx.core.json.JsonObject)member.getValue())); - } - break; - case "trustAll": - if (member.getValue() instanceof Boolean) { - obj.setTrustAll((Boolean)member.getValue()); + case "localAddress": + if (member.getValue() instanceof String) { + obj.setLocalAddress((String)member.getValue()); } break; } @@ -64,21 +64,21 @@ static void toJson(ClientOptionsBase obj, JsonObject json) { } static void toJson(ClientOptionsBase obj, java.util.Map json) { + json.put("trustAll", obj.isTrustAll()); json.put("connectTimeout", obj.getConnectTimeout()); - if (obj.getLocalAddress() != null) { - json.put("localAddress", obj.getLocalAddress()); - } if (obj.getMetricsName() != null) { json.put("metricsName", obj.getMetricsName()); } + if (obj.getProxyOptions() != null) { + json.put("proxyOptions", obj.getProxyOptions().toJson()); + } if (obj.getNonProxyHosts() != null) { JsonArray array = new JsonArray(); obj.getNonProxyHosts().forEach(item -> array.add(item)); json.put("nonProxyHosts", array); } - if (obj.getProxyOptions() != null) { - json.put("proxyOptions", obj.getProxyOptions().toJson()); + if (obj.getLocalAddress() != null) { + json.put("localAddress", obj.getLocalAddress()); } - json.put("trustAll", obj.isTrustAll()); } } diff --git a/src/main/generated/io/vertx/core/net/ClientSSLOptionsConverter.java b/src/main/generated/io/vertx/core/net/ClientSSLOptionsConverter.java new file mode 100644 index 00000000000..d3127bf9991 --- /dev/null +++ b/src/main/generated/io/vertx/core/net/ClientSSLOptionsConverter.java @@ -0,0 +1,47 @@ +package io.vertx.core.net; + +import io.vertx.core.json.JsonObject; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.impl.JsonUtil; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.Base64; + +/** + * Converter and mapper for {@link io.vertx.core.net.ClientSSLOptions}. + * NOTE: This class has been automatically generated from the {@link io.vertx.core.net.ClientSSLOptions} original class using Vert.x codegen. + */ +public class ClientSSLOptionsConverter { + + + private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER; + private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER; + + static void fromJson(Iterable> json, ClientSSLOptions obj) { + for (java.util.Map.Entry member : json) { + switch (member.getKey()) { + case "hostnameVerificationAlgorithm": + if (member.getValue() instanceof String) { + obj.setHostnameVerificationAlgorithm((String)member.getValue()); + } + break; + case "trustAll": + if (member.getValue() instanceof Boolean) { + obj.setTrustAll((Boolean)member.getValue()); + } + break; + } + } + } + + static void toJson(ClientSSLOptions obj, JsonObject json) { + toJson(obj, json.getMap()); + } + + static void toJson(ClientSSLOptions obj, java.util.Map json) { + if (obj.getHostnameVerificationAlgorithm() != null) { + json.put("hostnameVerificationAlgorithm", obj.getHostnameVerificationAlgorithm()); + } + json.put("trustAll", obj.isTrustAll()); + } +} diff --git a/src/main/generated/io/vertx/core/net/JksOptionsConverter.java b/src/main/generated/io/vertx/core/net/JksOptionsConverter.java index 20fbee73c96..fa19d8a4e37 100644 --- a/src/main/generated/io/vertx/core/net/JksOptionsConverter.java +++ b/src/main/generated/io/vertx/core/net/JksOptionsConverter.java @@ -17,19 +17,9 @@ public class JksOptionsConverter { private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER; private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER; - public static void fromJson(Iterable> json, JksOptions obj) { + static void fromJson(Iterable> json, JksOptions obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { - case "alias": - if (member.getValue() instanceof String) { - obj.setAlias((String)member.getValue()); - } - break; - case "aliasPassword": - if (member.getValue() instanceof String) { - obj.setAliasPassword((String)member.getValue()); - } - break; case "password": if (member.getValue() instanceof String) { obj.setPassword((String)member.getValue()); @@ -45,21 +35,25 @@ public static void fromJson(Iterable> json, obj.setValue(io.vertx.core.buffer.Buffer.buffer(BASE64_DECODER.decode((String)member.getValue()))); } break; + case "alias": + if (member.getValue() instanceof String) { + obj.setAlias((String)member.getValue()); + } + break; + case "aliasPassword": + if (member.getValue() instanceof String) { + obj.setAliasPassword((String)member.getValue()); + } + break; } } } - public static void toJson(JksOptions obj, JsonObject json) { + static void toJson(JksOptions obj, JsonObject json) { toJson(obj, json.getMap()); } - public static void toJson(JksOptions obj, java.util.Map json) { - if (obj.getAlias() != null) { - json.put("alias", obj.getAlias()); - } - if (obj.getAliasPassword() != null) { - json.put("aliasPassword", obj.getAliasPassword()); - } + static void toJson(JksOptions obj, java.util.Map json) { if (obj.getPassword() != null) { json.put("password", obj.getPassword()); } @@ -69,5 +63,11 @@ public static void toJson(JksOptions obj, java.util.Map json) { if (obj.getValue() != null) { json.put("value", BASE64_ENCODER.encodeToString(obj.getValue().getBytes())); } + if (obj.getAlias() != null) { + json.put("alias", obj.getAlias()); + } + if (obj.getAliasPassword() != null) { + json.put("aliasPassword", obj.getAliasPassword()); + } } } diff --git a/src/main/generated/io/vertx/core/net/KeyStoreOptionsConverter.java b/src/main/generated/io/vertx/core/net/KeyStoreOptionsConverter.java index 0af21906eb6..0345c13ebf3 100644 --- a/src/main/generated/io/vertx/core/net/KeyStoreOptionsConverter.java +++ b/src/main/generated/io/vertx/core/net/KeyStoreOptionsConverter.java @@ -17,27 +17,32 @@ public class KeyStoreOptionsConverter { private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER; private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER; - public static void fromJson(Iterable> json, KeyStoreOptions obj) { + static void fromJson(Iterable> json, KeyStoreOptions obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { - case "alias": + case "password": if (member.getValue() instanceof String) { - obj.setAlias((String)member.getValue()); + obj.setPassword((String)member.getValue()); } break; - case "aliasPassword": + case "path": if (member.getValue() instanceof String) { - obj.setAliasPassword((String)member.getValue()); + obj.setPath((String)member.getValue()); } break; - case "password": + case "value": if (member.getValue() instanceof String) { - obj.setPassword((String)member.getValue()); + obj.setValue(io.vertx.core.buffer.Buffer.buffer(BASE64_DECODER.decode((String)member.getValue()))); } break; - case "path": + case "alias": if (member.getValue() instanceof String) { - obj.setPath((String)member.getValue()); + obj.setAlias((String)member.getValue()); + } + break; + case "aliasPassword": + if (member.getValue() instanceof String) { + obj.setAliasPassword((String)member.getValue()); } break; case "provider": @@ -50,40 +55,35 @@ public static void fromJson(Iterable> json, obj.setType((String)member.getValue()); } break; - case "value": - if (member.getValue() instanceof String) { - obj.setValue(io.vertx.core.buffer.Buffer.buffer(BASE64_DECODER.decode((String)member.getValue()))); - } - break; } } } - public static void toJson(KeyStoreOptions obj, JsonObject json) { + static void toJson(KeyStoreOptions obj, JsonObject json) { toJson(obj, json.getMap()); } - public static void toJson(KeyStoreOptions obj, java.util.Map json) { - if (obj.getAlias() != null) { - json.put("alias", obj.getAlias()); - } - if (obj.getAliasPassword() != null) { - json.put("aliasPassword", obj.getAliasPassword()); - } + static void toJson(KeyStoreOptions obj, java.util.Map json) { if (obj.getPassword() != null) { json.put("password", obj.getPassword()); } if (obj.getPath() != null) { json.put("path", obj.getPath()); } + if (obj.getValue() != null) { + json.put("value", BASE64_ENCODER.encodeToString(obj.getValue().getBytes())); + } + if (obj.getAlias() != null) { + json.put("alias", obj.getAlias()); + } + if (obj.getAliasPassword() != null) { + json.put("aliasPassword", obj.getAliasPassword()); + } if (obj.getProvider() != null) { json.put("provider", obj.getProvider()); } if (obj.getType() != null) { json.put("type", obj.getType()); } - if (obj.getValue() != null) { - json.put("value", BASE64_ENCODER.encodeToString(obj.getValue().getBytes())); - } } } diff --git a/src/main/generated/io/vertx/core/net/NetClientOptionsConverter.java b/src/main/generated/io/vertx/core/net/NetClientOptionsConverter.java index 9e5de2aa41c..875024713e8 100644 --- a/src/main/generated/io/vertx/core/net/NetClientOptionsConverter.java +++ b/src/main/generated/io/vertx/core/net/NetClientOptionsConverter.java @@ -20,21 +20,6 @@ public class NetClientOptionsConverter { static void fromJson(Iterable> json, NetClientOptions obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { - case "applicationLayerProtocols": - if (member.getValue() instanceof JsonArray) { - java.util.ArrayList list = new java.util.ArrayList<>(); - ((Iterable)member.getValue()).forEach( item -> { - if (item instanceof String) - list.add((String)item); - }); - obj.setApplicationLayerProtocols(list); - } - break; - case "hostnameVerificationAlgorithm": - if (member.getValue() instanceof String) { - obj.setHostnameVerificationAlgorithm((String)member.getValue()); - } - break; case "reconnectAttempts": if (member.getValue() instanceof Number) { obj.setReconnectAttempts(((Number)member.getValue()).intValue()); @@ -45,6 +30,21 @@ static void fromJson(Iterable> json, NetClie obj.setReconnectInterval(((Number)member.getValue()).longValue()); } break; + case "hostnameVerificationAlgorithm": + if (member.getValue() instanceof String) { + obj.setHostnameVerificationAlgorithm((String)member.getValue()); + } + break; + case "applicationLayerProtocols": + if (member.getValue() instanceof JsonArray) { + java.util.ArrayList list = new java.util.ArrayList<>(); + ((Iterable)member.getValue()).forEach( item -> { + if (item instanceof String) + list.add((String)item); + }); + obj.setApplicationLayerProtocols(list); + } + break; case "registerWriteHandler": if (member.getValue() instanceof Boolean) { obj.setRegisterWriteHandler((Boolean)member.getValue()); @@ -59,16 +59,16 @@ static void toJson(NetClientOptions obj, JsonObject json) { } static void toJson(NetClientOptions obj, java.util.Map json) { + json.put("reconnectAttempts", obj.getReconnectAttempts()); + json.put("reconnectInterval", obj.getReconnectInterval()); + if (obj.getHostnameVerificationAlgorithm() != null) { + json.put("hostnameVerificationAlgorithm", obj.getHostnameVerificationAlgorithm()); + } if (obj.getApplicationLayerProtocols() != null) { JsonArray array = new JsonArray(); obj.getApplicationLayerProtocols().forEach(item -> array.add(item)); json.put("applicationLayerProtocols", array); } - if (obj.getHostnameVerificationAlgorithm() != null) { - json.put("hostnameVerificationAlgorithm", obj.getHostnameVerificationAlgorithm()); - } - json.put("reconnectAttempts", obj.getReconnectAttempts()); - json.put("reconnectInterval", obj.getReconnectInterval()); json.put("registerWriteHandler", obj.isRegisterWriteHandler()); } } diff --git a/src/main/generated/io/vertx/core/net/NetServerOptionsConverter.java b/src/main/generated/io/vertx/core/net/NetServerOptionsConverter.java index a6498ea44ee..b713c682cc9 100644 --- a/src/main/generated/io/vertx/core/net/NetServerOptionsConverter.java +++ b/src/main/generated/io/vertx/core/net/NetServerOptionsConverter.java @@ -25,9 +25,9 @@ static void fromJson(Iterable> json, NetServ obj.setAcceptBacklog(((Number)member.getValue()).intValue()); } break; - case "clientAuth": - if (member.getValue() instanceof String) { - obj.setClientAuth(io.vertx.core.http.ClientAuth.valueOf((String)member.getValue())); + case "port": + if (member.getValue() instanceof Number) { + obj.setPort(((Number)member.getValue()).intValue()); } break; case "host": @@ -35,9 +35,19 @@ static void fromJson(Iterable> json, NetServ obj.setHost((String)member.getValue()); } break; - case "port": - if (member.getValue() instanceof Number) { - obj.setPort(((Number)member.getValue()).intValue()); + case "clientAuth": + if (member.getValue() instanceof String) { + obj.setClientAuth(io.vertx.core.http.ClientAuth.valueOf((String)member.getValue())); + } + break; + case "sni": + if (member.getValue() instanceof Boolean) { + obj.setSni((Boolean)member.getValue()); + } + break; + case "useProxyProtocol": + if (member.getValue() instanceof Boolean) { + obj.setUseProxyProtocol((Boolean)member.getValue()); } break; case "proxyProtocolTimeout": @@ -50,24 +60,14 @@ static void fromJson(Iterable> json, NetServ obj.setProxyProtocolTimeoutUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); } break; - case "registerWriteHandler": - if (member.getValue() instanceof Boolean) { - obj.setRegisterWriteHandler((Boolean)member.getValue()); - } - break; - case "sni": - if (member.getValue() instanceof Boolean) { - obj.setSni((Boolean)member.getValue()); - } - break; case "trafficShapingOptions": if (member.getValue() instanceof JsonObject) { obj.setTrafficShapingOptions(new io.vertx.core.net.TrafficShapingOptions((io.vertx.core.json.JsonObject)member.getValue())); } break; - case "useProxyProtocol": + case "registerWriteHandler": if (member.getValue() instanceof Boolean) { - obj.setUseProxyProtocol((Boolean)member.getValue()); + obj.setRegisterWriteHandler((Boolean)member.getValue()); } break; } @@ -80,22 +80,22 @@ static void toJson(NetServerOptions obj, JsonObject json) { static void toJson(NetServerOptions obj, java.util.Map json) { json.put("acceptBacklog", obj.getAcceptBacklog()); - if (obj.getClientAuth() != null) { - json.put("clientAuth", obj.getClientAuth().name()); - } + json.put("port", obj.getPort()); if (obj.getHost() != null) { json.put("host", obj.getHost()); } - json.put("port", obj.getPort()); + if (obj.getClientAuth() != null) { + json.put("clientAuth", obj.getClientAuth().name()); + } + json.put("sni", obj.isSni()); + json.put("useProxyProtocol", obj.isUseProxyProtocol()); json.put("proxyProtocolTimeout", obj.getProxyProtocolTimeout()); if (obj.getProxyProtocolTimeoutUnit() != null) { json.put("proxyProtocolTimeoutUnit", obj.getProxyProtocolTimeoutUnit().name()); } - json.put("registerWriteHandler", obj.isRegisterWriteHandler()); - json.put("sni", obj.isSni()); if (obj.getTrafficShapingOptions() != null) { json.put("trafficShapingOptions", obj.getTrafficShapingOptions().toJson()); } - json.put("useProxyProtocol", obj.isUseProxyProtocol()); + json.put("registerWriteHandler", obj.isRegisterWriteHandler()); } } diff --git a/src/main/generated/io/vertx/core/net/NetworkOptionsConverter.java b/src/main/generated/io/vertx/core/net/NetworkOptionsConverter.java index 40702b101d3..b19d067acea 100644 --- a/src/main/generated/io/vertx/core/net/NetworkOptionsConverter.java +++ b/src/main/generated/io/vertx/core/net/NetworkOptionsConverter.java @@ -20,14 +20,9 @@ public class NetworkOptionsConverter { static void fromJson(Iterable> json, NetworkOptions obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { - case "activityLogDataFormat": - if (member.getValue() instanceof String) { - obj.setActivityLogDataFormat(io.netty.handler.logging.ByteBufFormat.valueOf((String)member.getValue())); - } - break; - case "logActivity": - if (member.getValue() instanceof Boolean) { - obj.setLogActivity((Boolean)member.getValue()); + case "sendBufferSize": + if (member.getValue() instanceof Number) { + obj.setSendBufferSize(((Number)member.getValue()).intValue()); } break; case "receiveBufferSize": @@ -40,19 +35,24 @@ static void fromJson(Iterable> json, Network obj.setReuseAddress((Boolean)member.getValue()); } break; - case "reusePort": + case "trafficClass": + if (member.getValue() instanceof Number) { + obj.setTrafficClass(((Number)member.getValue()).intValue()); + } + break; + case "logActivity": if (member.getValue() instanceof Boolean) { - obj.setReusePort((Boolean)member.getValue()); + obj.setLogActivity((Boolean)member.getValue()); } break; - case "sendBufferSize": - if (member.getValue() instanceof Number) { - obj.setSendBufferSize(((Number)member.getValue()).intValue()); + case "activityLogDataFormat": + if (member.getValue() instanceof String) { + obj.setActivityLogDataFormat(io.netty.handler.logging.ByteBufFormat.valueOf((String)member.getValue())); } break; - case "trafficClass": - if (member.getValue() instanceof Number) { - obj.setTrafficClass(((Number)member.getValue()).intValue()); + case "reusePort": + if (member.getValue() instanceof Boolean) { + obj.setReusePort((Boolean)member.getValue()); } break; } @@ -64,14 +64,14 @@ static void toJson(NetworkOptions obj, JsonObject json) { } static void toJson(NetworkOptions obj, java.util.Map json) { + json.put("sendBufferSize", obj.getSendBufferSize()); + json.put("receiveBufferSize", obj.getReceiveBufferSize()); + json.put("reuseAddress", obj.isReuseAddress()); + json.put("trafficClass", obj.getTrafficClass()); + json.put("logActivity", obj.getLogActivity()); if (obj.getActivityLogDataFormat() != null) { json.put("activityLogDataFormat", obj.getActivityLogDataFormat().name()); } - json.put("logActivity", obj.getLogActivity()); - json.put("receiveBufferSize", obj.getReceiveBufferSize()); - json.put("reuseAddress", obj.isReuseAddress()); json.put("reusePort", obj.isReusePort()); - json.put("sendBufferSize", obj.getSendBufferSize()); - json.put("trafficClass", obj.getTrafficClass()); } } diff --git a/src/main/generated/io/vertx/core/net/OpenSSLEngineOptionsConverter.java b/src/main/generated/io/vertx/core/net/OpenSSLEngineOptionsConverter.java index 89646ffac29..29083d3ed9b 100644 --- a/src/main/generated/io/vertx/core/net/OpenSSLEngineOptionsConverter.java +++ b/src/main/generated/io/vertx/core/net/OpenSSLEngineOptionsConverter.java @@ -20,14 +20,14 @@ public class OpenSSLEngineOptionsConverter { static void fromJson(Iterable> json, OpenSSLEngineOptions obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { - case "sessionCacheEnabled": + case "useWorkerThread": if (member.getValue() instanceof Boolean) { - obj.setSessionCacheEnabled((Boolean)member.getValue()); + obj.setUseWorkerThread((Boolean)member.getValue()); } break; - case "useWorkerThread": + case "sessionCacheEnabled": if (member.getValue() instanceof Boolean) { - obj.setUseWorkerThread((Boolean)member.getValue()); + obj.setSessionCacheEnabled((Boolean)member.getValue()); } break; } @@ -39,7 +39,7 @@ static void toJson(OpenSSLEngineOptions obj, JsonObject json) { } static void toJson(OpenSSLEngineOptions obj, java.util.Map json) { - json.put("sessionCacheEnabled", obj.isSessionCacheEnabled()); json.put("useWorkerThread", obj.getUseWorkerThread()); + json.put("sessionCacheEnabled", obj.isSessionCacheEnabled()); } } diff --git a/src/main/generated/io/vertx/core/net/PemKeyCertOptionsConverter.java b/src/main/generated/io/vertx/core/net/PemKeyCertOptionsConverter.java index b2d36deed5f..5ba644e897e 100644 --- a/src/main/generated/io/vertx/core/net/PemKeyCertOptionsConverter.java +++ b/src/main/generated/io/vertx/core/net/PemKeyCertOptionsConverter.java @@ -20,64 +20,64 @@ public class PemKeyCertOptionsConverter { static void fromJson(Iterable> json, PemKeyCertOptions obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { - case "certPath": + case "keyPath": if (member.getValue() instanceof String) { - obj.setCertPath((String)member.getValue()); + obj.setKeyPath((String)member.getValue()); } break; - case "certPaths": + case "keyPaths": if (member.getValue() instanceof JsonArray) { java.util.ArrayList list = new java.util.ArrayList<>(); ((Iterable)member.getValue()).forEach( item -> { if (item instanceof String) list.add((String)item); }); - obj.setCertPaths(list); + obj.setKeyPaths(list); } break; - case "certValue": + case "keyValue": if (member.getValue() instanceof String) { - obj.setCertValue(io.vertx.core.buffer.Buffer.buffer(BASE64_DECODER.decode((String)member.getValue()))); + obj.setKeyValue(io.vertx.core.buffer.Buffer.buffer(BASE64_DECODER.decode((String)member.getValue()))); } break; - case "certValues": + case "keyValues": if (member.getValue() instanceof JsonArray) { java.util.ArrayList list = new java.util.ArrayList<>(); ((Iterable)member.getValue()).forEach( item -> { if (item instanceof String) list.add(io.vertx.core.buffer.Buffer.buffer(BASE64_DECODER.decode((String)item))); }); - obj.setCertValues(list); + obj.setKeyValues(list); } break; - case "keyPath": + case "certPath": if (member.getValue() instanceof String) { - obj.setKeyPath((String)member.getValue()); + obj.setCertPath((String)member.getValue()); } break; - case "keyPaths": + case "certPaths": if (member.getValue() instanceof JsonArray) { java.util.ArrayList list = new java.util.ArrayList<>(); ((Iterable)member.getValue()).forEach( item -> { if (item instanceof String) list.add((String)item); }); - obj.setKeyPaths(list); + obj.setCertPaths(list); } break; - case "keyValue": + case "certValue": if (member.getValue() instanceof String) { - obj.setKeyValue(io.vertx.core.buffer.Buffer.buffer(BASE64_DECODER.decode((String)member.getValue()))); + obj.setCertValue(io.vertx.core.buffer.Buffer.buffer(BASE64_DECODER.decode((String)member.getValue()))); } break; - case "keyValues": + case "certValues": if (member.getValue() instanceof JsonArray) { java.util.ArrayList list = new java.util.ArrayList<>(); ((Iterable)member.getValue()).forEach( item -> { if (item instanceof String) list.add(io.vertx.core.buffer.Buffer.buffer(BASE64_DECODER.decode((String)item))); }); - obj.setKeyValues(list); + obj.setCertValues(list); } break; } @@ -89,16 +89,6 @@ static void toJson(PemKeyCertOptions obj, JsonObject json) { } static void toJson(PemKeyCertOptions obj, java.util.Map json) { - if (obj.getCertPaths() != null) { - JsonArray array = new JsonArray(); - obj.getCertPaths().forEach(item -> array.add(item)); - json.put("certPaths", array); - } - if (obj.getCertValues() != null) { - JsonArray array = new JsonArray(); - obj.getCertValues().forEach(item -> array.add(BASE64_ENCODER.encodeToString(item.getBytes()))); - json.put("certValues", array); - } if (obj.getKeyPaths() != null) { JsonArray array = new JsonArray(); obj.getKeyPaths().forEach(item -> array.add(item)); @@ -109,5 +99,15 @@ static void toJson(PemKeyCertOptions obj, java.util.Map json) { obj.getKeyValues().forEach(item -> array.add(BASE64_ENCODER.encodeToString(item.getBytes()))); json.put("keyValues", array); } + if (obj.getCertPaths() != null) { + JsonArray array = new JsonArray(); + obj.getCertPaths().forEach(item -> array.add(item)); + json.put("certPaths", array); + } + if (obj.getCertValues() != null) { + JsonArray array = new JsonArray(); + obj.getCertValues().forEach(item -> array.add(BASE64_ENCODER.encodeToString(item.getBytes()))); + json.put("certValues", array); + } } } diff --git a/src/main/generated/io/vertx/core/net/PfxOptionsConverter.java b/src/main/generated/io/vertx/core/net/PfxOptionsConverter.java index 96c6a009d87..1d412af6f40 100644 --- a/src/main/generated/io/vertx/core/net/PfxOptionsConverter.java +++ b/src/main/generated/io/vertx/core/net/PfxOptionsConverter.java @@ -20,16 +20,6 @@ public class PfxOptionsConverter { static void fromJson(Iterable> json, PfxOptions obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { - case "alias": - if (member.getValue() instanceof String) { - obj.setAlias((String)member.getValue()); - } - break; - case "aliasPassword": - if (member.getValue() instanceof String) { - obj.setAliasPassword((String)member.getValue()); - } - break; case "password": if (member.getValue() instanceof String) { obj.setPassword((String)member.getValue()); @@ -45,6 +35,16 @@ static void fromJson(Iterable> json, PfxOpti obj.setValue(io.vertx.core.buffer.Buffer.buffer(BASE64_DECODER.decode((String)member.getValue()))); } break; + case "alias": + if (member.getValue() instanceof String) { + obj.setAlias((String)member.getValue()); + } + break; + case "aliasPassword": + if (member.getValue() instanceof String) { + obj.setAliasPassword((String)member.getValue()); + } + break; } } } @@ -54,12 +54,6 @@ static void toJson(PfxOptions obj, JsonObject json) { } static void toJson(PfxOptions obj, java.util.Map json) { - if (obj.getAlias() != null) { - json.put("alias", obj.getAlias()); - } - if (obj.getAliasPassword() != null) { - json.put("aliasPassword", obj.getAliasPassword()); - } if (obj.getPassword() != null) { json.put("password", obj.getPassword()); } @@ -69,5 +63,11 @@ static void toJson(PfxOptions obj, java.util.Map json) { if (obj.getValue() != null) { json.put("value", BASE64_ENCODER.encodeToString(obj.getValue().getBytes())); } + if (obj.getAlias() != null) { + json.put("alias", obj.getAlias()); + } + if (obj.getAliasPassword() != null) { + json.put("aliasPassword", obj.getAliasPassword()); + } } } diff --git a/src/main/generated/io/vertx/core/net/ProxyOptionsConverter.java b/src/main/generated/io/vertx/core/net/ProxyOptionsConverter.java index f26faa194ba..d4db49e3fd3 100644 --- a/src/main/generated/io/vertx/core/net/ProxyOptionsConverter.java +++ b/src/main/generated/io/vertx/core/net/ProxyOptionsConverter.java @@ -25,24 +25,24 @@ static void fromJson(Iterable> json, ProxyOp obj.setHost((String)member.getValue()); } break; - case "password": - if (member.getValue() instanceof String) { - obj.setPassword((String)member.getValue()); - } - break; case "port": if (member.getValue() instanceof Number) { obj.setPort(((Number)member.getValue()).intValue()); } break; - case "type": + case "username": if (member.getValue() instanceof String) { - obj.setType(io.vertx.core.net.ProxyType.valueOf((String)member.getValue())); + obj.setUsername((String)member.getValue()); } break; - case "username": + case "password": if (member.getValue() instanceof String) { - obj.setUsername((String)member.getValue()); + obj.setPassword((String)member.getValue()); + } + break; + case "type": + if (member.getValue() instanceof String) { + obj.setType(io.vertx.core.net.ProxyType.valueOf((String)member.getValue())); } break; } @@ -57,15 +57,15 @@ static void toJson(ProxyOptions obj, java.util.Map json) { if (obj.getHost() != null) { json.put("host", obj.getHost()); } + json.put("port", obj.getPort()); + if (obj.getUsername() != null) { + json.put("username", obj.getUsername()); + } if (obj.getPassword() != null) { json.put("password", obj.getPassword()); } - json.put("port", obj.getPort()); if (obj.getType() != null) { json.put("type", obj.getType().name()); } - if (obj.getUsername() != null) { - json.put("username", obj.getUsername()); - } } } diff --git a/src/main/generated/io/vertx/core/net/SSLOptionsConverter.java b/src/main/generated/io/vertx/core/net/SSLOptionsConverter.java index ed5e57e40c9..b67f9df6448 100644 --- a/src/main/generated/io/vertx/core/net/SSLOptionsConverter.java +++ b/src/main/generated/io/vertx/core/net/SSLOptionsConverter.java @@ -20,30 +20,35 @@ public class SSLOptionsConverter { static void fromJson(Iterable> json, SSLOptions obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { - case "crlPaths": + case "enabledCipherSuites": if (member.getValue() instanceof JsonArray) { ((Iterable)member.getValue()).forEach( item -> { if (item instanceof String) - obj.addCrlPath((String)item); + obj.addEnabledCipherSuite((String)item); }); } break; - case "crlValues": + case "crlPaths": if (member.getValue() instanceof JsonArray) { ((Iterable)member.getValue()).forEach( item -> { if (item instanceof String) - obj.addCrlValue(io.vertx.core.buffer.Buffer.buffer(BASE64_DECODER.decode((String)item))); + obj.addCrlPath((String)item); }); } break; - case "enabledCipherSuites": + case "crlValues": if (member.getValue() instanceof JsonArray) { ((Iterable)member.getValue()).forEach( item -> { if (item instanceof String) - obj.addEnabledCipherSuite((String)item); + obj.addCrlValue(io.vertx.core.buffer.Buffer.buffer(BASE64_DECODER.decode((String)item))); }); } break; + case "useAlpn": + if (member.getValue() instanceof Boolean) { + obj.setUseAlpn((Boolean)member.getValue()); + } + break; case "enabledSecureTransportProtocols": if (member.getValue() instanceof JsonArray) { java.util.LinkedHashSet list = new java.util.LinkedHashSet<>(); @@ -64,9 +69,14 @@ static void fromJson(Iterable> json, SSLOpti obj.setSslHandshakeTimeoutUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); } break; - case "useAlpn": - if (member.getValue() instanceof Boolean) { - obj.setUseAlpn((Boolean)member.getValue()); + case "applicationLayerProtocols": + if (member.getValue() instanceof JsonArray) { + java.util.ArrayList list = new java.util.ArrayList<>(); + ((Iterable)member.getValue()).forEach( item -> { + if (item instanceof String) + list.add((String)item); + }); + obj.setApplicationLayerProtocols(list); } break; } @@ -78,6 +88,11 @@ static void toJson(SSLOptions obj, JsonObject json) { } static void toJson(SSLOptions obj, java.util.Map json) { + if (obj.getEnabledCipherSuites() != null) { + JsonArray array = new JsonArray(); + obj.getEnabledCipherSuites().forEach(item -> array.add(item)); + json.put("enabledCipherSuites", array); + } if (obj.getCrlPaths() != null) { JsonArray array = new JsonArray(); obj.getCrlPaths().forEach(item -> array.add(item)); @@ -88,11 +103,7 @@ static void toJson(SSLOptions obj, java.util.Map json) { obj.getCrlValues().forEach(item -> array.add(BASE64_ENCODER.encodeToString(item.getBytes()))); json.put("crlValues", array); } - if (obj.getEnabledCipherSuites() != null) { - JsonArray array = new JsonArray(); - obj.getEnabledCipherSuites().forEach(item -> array.add(item)); - json.put("enabledCipherSuites", array); - } + json.put("useAlpn", obj.isUseAlpn()); if (obj.getEnabledSecureTransportProtocols() != null) { JsonArray array = new JsonArray(); obj.getEnabledSecureTransportProtocols().forEach(item -> array.add(item)); @@ -102,6 +113,10 @@ static void toJson(SSLOptions obj, java.util.Map json) { if (obj.getSslHandshakeTimeoutUnit() != null) { json.put("sslHandshakeTimeoutUnit", obj.getSslHandshakeTimeoutUnit().name()); } - json.put("useAlpn", obj.isUseAlpn()); + if (obj.getApplicationLayerProtocols() != null) { + JsonArray array = new JsonArray(); + obj.getApplicationLayerProtocols().forEach(item -> array.add(item)); + json.put("applicationLayerProtocols", array); + } } } diff --git a/src/main/generated/io/vertx/core/net/ServerSSLOptionsConverter.java b/src/main/generated/io/vertx/core/net/ServerSSLOptionsConverter.java new file mode 100644 index 00000000000..0eaee092daa --- /dev/null +++ b/src/main/generated/io/vertx/core/net/ServerSSLOptionsConverter.java @@ -0,0 +1,47 @@ +package io.vertx.core.net; + +import io.vertx.core.json.JsonObject; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.impl.JsonUtil; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.Base64; + +/** + * Converter and mapper for {@link io.vertx.core.net.ServerSSLOptions}. + * NOTE: This class has been automatically generated from the {@link io.vertx.core.net.ServerSSLOptions} original class using Vert.x codegen. + */ +public class ServerSSLOptionsConverter { + + + private static final Base64.Decoder BASE64_DECODER = JsonUtil.BASE64_DECODER; + private static final Base64.Encoder BASE64_ENCODER = JsonUtil.BASE64_ENCODER; + + static void fromJson(Iterable> json, ServerSSLOptions obj) { + for (java.util.Map.Entry member : json) { + switch (member.getKey()) { + case "clientAuth": + if (member.getValue() instanceof String) { + obj.setClientAuth(io.vertx.core.http.ClientAuth.valueOf((String)member.getValue())); + } + break; + case "sni": + if (member.getValue() instanceof Boolean) { + obj.setSni((Boolean)member.getValue()); + } + break; + } + } + } + + static void toJson(ServerSSLOptions obj, JsonObject json) { + toJson(obj, json.getMap()); + } + + static void toJson(ServerSSLOptions obj, java.util.Map json) { + if (obj.getClientAuth() != null) { + json.put("clientAuth", obj.getClientAuth().name()); + } + json.put("sni", obj.isSni()); + } +} diff --git a/src/main/generated/io/vertx/core/net/TCPSSLOptionsConverter.java b/src/main/generated/io/vertx/core/net/TCPSSLOptionsConverter.java index d15b6de6178..94234959cf6 100644 --- a/src/main/generated/io/vertx/core/net/TCPSSLOptionsConverter.java +++ b/src/main/generated/io/vertx/core/net/TCPSSLOptionsConverter.java @@ -20,38 +20,19 @@ public class TCPSSLOptionsConverter { static void fromJson(Iterable> json, TCPSSLOptions obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { - case "crlPaths": - if (member.getValue() instanceof JsonArray) { - ((Iterable)member.getValue()).forEach( item -> { - if (item instanceof String) - obj.addCrlPath((String)item); - }); - } - break; - case "crlValues": - if (member.getValue() instanceof JsonArray) { - ((Iterable)member.getValue()).forEach( item -> { - if (item instanceof String) - obj.addCrlValue(io.vertx.core.buffer.Buffer.buffer(BASE64_DECODER.decode((String)item))); - }); + case "tcpNoDelay": + if (member.getValue() instanceof Boolean) { + obj.setTcpNoDelay((Boolean)member.getValue()); } break; - case "enabledCipherSuites": - if (member.getValue() instanceof JsonArray) { - ((Iterable)member.getValue()).forEach( item -> { - if (item instanceof String) - obj.addEnabledCipherSuite((String)item); - }); + case "tcpKeepAlive": + if (member.getValue() instanceof Boolean) { + obj.setTcpKeepAlive((Boolean)member.getValue()); } break; - case "enabledSecureTransportProtocols": - if (member.getValue() instanceof JsonArray) { - java.util.LinkedHashSet list = new java.util.LinkedHashSet<>(); - ((Iterable)member.getValue()).forEach( item -> { - if (item instanceof String) - list.add((String)item); - }); - obj.setEnabledSecureTransportProtocols(list); + case "soLinger": + if (member.getValue() instanceof Number) { + obj.setSoLinger(((Number)member.getValue()).intValue()); } break; case "idleTimeout": @@ -59,54 +40,19 @@ static void fromJson(Iterable> json, TCPSSLO obj.setIdleTimeout(((Number)member.getValue()).intValue()); } break; - case "idleTimeoutUnit": - if (member.getValue() instanceof String) { - obj.setIdleTimeoutUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); - } - break; - case "jdkSslEngineOptions": - if (member.getValue() instanceof JsonObject) { - obj.setJdkSslEngineOptions(new io.vertx.core.net.JdkSSLEngineOptions((io.vertx.core.json.JsonObject)member.getValue())); - } - break; - case "keyStoreOptions": - if (member.getValue() instanceof JsonObject) { - obj.setKeyStoreOptions(new io.vertx.core.net.JksOptions((io.vertx.core.json.JsonObject)member.getValue())); - } - break; - case "openSslEngineOptions": - if (member.getValue() instanceof JsonObject) { - obj.setOpenSslEngineOptions(new io.vertx.core.net.OpenSSLEngineOptions((io.vertx.core.json.JsonObject)member.getValue())); - } - break; - case "pemKeyCertOptions": - if (member.getValue() instanceof JsonObject) { - obj.setPemKeyCertOptions(new io.vertx.core.net.PemKeyCertOptions((io.vertx.core.json.JsonObject)member.getValue())); - } - break; - case "pemTrustOptions": - if (member.getValue() instanceof JsonObject) { - obj.setPemTrustOptions(new io.vertx.core.net.PemTrustOptions((io.vertx.core.json.JsonObject)member.getValue())); - } - break; - case "pfxKeyCertOptions": - if (member.getValue() instanceof JsonObject) { - obj.setPfxKeyCertOptions(new io.vertx.core.net.PfxOptions((io.vertx.core.json.JsonObject)member.getValue())); - } - break; - case "pfxTrustOptions": - if (member.getValue() instanceof JsonObject) { - obj.setPfxTrustOptions(new io.vertx.core.net.PfxOptions((io.vertx.core.json.JsonObject)member.getValue())); - } - break; case "readIdleTimeout": if (member.getValue() instanceof Number) { obj.setReadIdleTimeout(((Number)member.getValue()).intValue()); } break; - case "soLinger": + case "writeIdleTimeout": if (member.getValue() instanceof Number) { - obj.setSoLinger(((Number)member.getValue()).intValue()); + obj.setWriteIdleTimeout(((Number)member.getValue()).intValue()); + } + break; + case "idleTimeoutUnit": + if (member.getValue() instanceof String) { + obj.setIdleTimeoutUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); } break; case "ssl": @@ -114,34 +60,53 @@ static void fromJson(Iterable> json, TCPSSLO obj.setSsl((Boolean)member.getValue()); } break; - case "sslHandshakeTimeout": - if (member.getValue() instanceof Number) { - obj.setSslHandshakeTimeout(((Number)member.getValue()).longValue()); + case "enabledCipherSuites": + if (member.getValue() instanceof JsonArray) { + ((Iterable)member.getValue()).forEach( item -> { + if (item instanceof String) + obj.addEnabledCipherSuite((String)item); + }); } break; - case "sslHandshakeTimeoutUnit": - if (member.getValue() instanceof String) { - obj.setSslHandshakeTimeoutUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); + case "crlPaths": + if (member.getValue() instanceof JsonArray) { + ((Iterable)member.getValue()).forEach( item -> { + if (item instanceof String) + obj.addCrlPath((String)item); + }); } break; - case "tcpCork": - if (member.getValue() instanceof Boolean) { - obj.setTcpCork((Boolean)member.getValue()); + case "crlValues": + if (member.getValue() instanceof JsonArray) { + ((Iterable)member.getValue()).forEach( item -> { + if (item instanceof String) + obj.addCrlValue(io.vertx.core.buffer.Buffer.buffer(BASE64_DECODER.decode((String)item))); + }); } break; - case "tcpFastOpen": + case "useAlpn": if (member.getValue() instanceof Boolean) { - obj.setTcpFastOpen((Boolean)member.getValue()); + obj.setUseAlpn((Boolean)member.getValue()); } break; - case "tcpKeepAlive": + case "enabledSecureTransportProtocols": + if (member.getValue() instanceof JsonArray) { + java.util.LinkedHashSet list = new java.util.LinkedHashSet<>(); + ((Iterable)member.getValue()).forEach( item -> { + if (item instanceof String) + list.add((String)item); + }); + obj.setEnabledSecureTransportProtocols(list); + } + break; + case "tcpFastOpen": if (member.getValue() instanceof Boolean) { - obj.setTcpKeepAlive((Boolean)member.getValue()); + obj.setTcpFastOpen((Boolean)member.getValue()); } break; - case "tcpNoDelay": + case "tcpCork": if (member.getValue() instanceof Boolean) { - obj.setTcpNoDelay((Boolean)member.getValue()); + obj.setTcpCork((Boolean)member.getValue()); } break; case "tcpQuickAck": @@ -154,19 +119,14 @@ static void fromJson(Iterable> json, TCPSSLO obj.setTcpUserTimeout(((Number)member.getValue()).intValue()); } break; - case "trustStoreOptions": - if (member.getValue() instanceof JsonObject) { - obj.setTrustStoreOptions(new io.vertx.core.net.JksOptions((io.vertx.core.json.JsonObject)member.getValue())); - } - break; - case "useAlpn": - if (member.getValue() instanceof Boolean) { - obj.setUseAlpn((Boolean)member.getValue()); + case "sslHandshakeTimeout": + if (member.getValue() instanceof Number) { + obj.setSslHandshakeTimeout(((Number)member.getValue()).longValue()); } break; - case "writeIdleTimeout": - if (member.getValue() instanceof Number) { - obj.setWriteIdleTimeout(((Number)member.getValue()).intValue()); + case "sslHandshakeTimeoutUnit": + if (member.getValue() instanceof String) { + obj.setSslHandshakeTimeoutUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); } break; } @@ -178,6 +138,21 @@ static void toJson(TCPSSLOptions obj, JsonObject json) { } static void toJson(TCPSSLOptions obj, java.util.Map json) { + json.put("tcpNoDelay", obj.isTcpNoDelay()); + json.put("tcpKeepAlive", obj.isTcpKeepAlive()); + json.put("soLinger", obj.getSoLinger()); + json.put("idleTimeout", obj.getIdleTimeout()); + json.put("readIdleTimeout", obj.getReadIdleTimeout()); + json.put("writeIdleTimeout", obj.getWriteIdleTimeout()); + if (obj.getIdleTimeoutUnit() != null) { + json.put("idleTimeoutUnit", obj.getIdleTimeoutUnit().name()); + } + json.put("ssl", obj.isSsl()); + if (obj.getEnabledCipherSuites() != null) { + JsonArray array = new JsonArray(); + obj.getEnabledCipherSuites().forEach(item -> array.add(item)); + json.put("enabledCipherSuites", array); + } if (obj.getCrlPaths() != null) { JsonArray array = new JsonArray(); obj.getCrlPaths().forEach(item -> array.add(item)); @@ -188,58 +163,19 @@ static void toJson(TCPSSLOptions obj, java.util.Map json) { obj.getCrlValues().forEach(item -> array.add(BASE64_ENCODER.encodeToString(item.getBytes()))); json.put("crlValues", array); } - if (obj.getEnabledCipherSuites() != null) { - JsonArray array = new JsonArray(); - obj.getEnabledCipherSuites().forEach(item -> array.add(item)); - json.put("enabledCipherSuites", array); - } + json.put("useAlpn", obj.isUseAlpn()); if (obj.getEnabledSecureTransportProtocols() != null) { JsonArray array = new JsonArray(); obj.getEnabledSecureTransportProtocols().forEach(item -> array.add(item)); json.put("enabledSecureTransportProtocols", array); } - json.put("idleTimeout", obj.getIdleTimeout()); - if (obj.getIdleTimeoutUnit() != null) { - json.put("idleTimeoutUnit", obj.getIdleTimeoutUnit().name()); - } - if (obj.getJdkSslEngineOptions() != null) { - json.put("jdkSslEngineOptions", obj.getJdkSslEngineOptions().toJson()); - } - if (obj.getKeyStoreOptions() != null) { - json.put("keyStoreOptions", obj.getKeyStoreOptions().toJson()); - } - if (obj.getOpenSslEngineOptions() != null) { - json.put("openSslEngineOptions", obj.getOpenSslEngineOptions().toJson()); - } - if (obj.getPemKeyCertOptions() != null) { - json.put("pemKeyCertOptions", obj.getPemKeyCertOptions().toJson()); - } - if (obj.getPemTrustOptions() != null) { - json.put("pemTrustOptions", obj.getPemTrustOptions().toJson()); - } - if (obj.getPfxKeyCertOptions() != null) { - json.put("pfxKeyCertOptions", obj.getPfxKeyCertOptions().toJson()); - } - if (obj.getPfxTrustOptions() != null) { - json.put("pfxTrustOptions", obj.getPfxTrustOptions().toJson()); - } - json.put("readIdleTimeout", obj.getReadIdleTimeout()); - json.put("soLinger", obj.getSoLinger()); - json.put("ssl", obj.isSsl()); - json.put("sslHandshakeTimeout", obj.getSslHandshakeTimeout()); - if (obj.getSslHandshakeTimeoutUnit() != null) { - json.put("sslHandshakeTimeoutUnit", obj.getSslHandshakeTimeoutUnit().name()); - } - json.put("tcpCork", obj.isTcpCork()); json.put("tcpFastOpen", obj.isTcpFastOpen()); - json.put("tcpKeepAlive", obj.isTcpKeepAlive()); - json.put("tcpNoDelay", obj.isTcpNoDelay()); + json.put("tcpCork", obj.isTcpCork()); json.put("tcpQuickAck", obj.isTcpQuickAck()); json.put("tcpUserTimeout", obj.getTcpUserTimeout()); - if (obj.getTrustStoreOptions() != null) { - json.put("trustStoreOptions", obj.getTrustStoreOptions().toJson()); + json.put("sslHandshakeTimeout", obj.getSslHandshakeTimeout()); + if (obj.getSslHandshakeTimeoutUnit() != null) { + json.put("sslHandshakeTimeoutUnit", obj.getSslHandshakeTimeoutUnit().name()); } - json.put("useAlpn", obj.isUseAlpn()); - json.put("writeIdleTimeout", obj.getWriteIdleTimeout()); } } diff --git a/src/main/generated/io/vertx/core/net/TrafficShapingOptionsConverter.java b/src/main/generated/io/vertx/core/net/TrafficShapingOptionsConverter.java index debc55df9de..938f3ed8993 100644 --- a/src/main/generated/io/vertx/core/net/TrafficShapingOptionsConverter.java +++ b/src/main/generated/io/vertx/core/net/TrafficShapingOptionsConverter.java @@ -20,19 +20,14 @@ public class TrafficShapingOptionsConverter { static void fromJson(Iterable> json, TrafficShapingOptions obj) { for (java.util.Map.Entry member : json) { switch (member.getKey()) { - case "checkIntervalForStats": + case "inboundGlobalBandwidth": if (member.getValue() instanceof Number) { - obj.setCheckIntervalForStats(((Number)member.getValue()).longValue()); - } - break; - case "checkIntervalForStatsTimeUnit": - if (member.getValue() instanceof String) { - obj.setCheckIntervalForStatsTimeUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); + obj.setInboundGlobalBandwidth(((Number)member.getValue()).longValue()); } break; - case "inboundGlobalBandwidth": + case "outboundGlobalBandwidth": if (member.getValue() instanceof Number) { - obj.setInboundGlobalBandwidth(((Number)member.getValue()).longValue()); + obj.setOutboundGlobalBandwidth(((Number)member.getValue()).longValue()); } break; case "maxDelayToWait": @@ -40,16 +35,19 @@ static void fromJson(Iterable> json, Traffic obj.setMaxDelayToWait(((Number)member.getValue()).longValue()); } break; - case "maxDelayToWaitTimeUnit": - break; case "maxDelayToWaitUnit": if (member.getValue() instanceof String) { obj.setMaxDelayToWaitUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); } break; - case "outboundGlobalBandwidth": + case "checkIntervalForStats": if (member.getValue() instanceof Number) { - obj.setOutboundGlobalBandwidth(((Number)member.getValue()).longValue()); + obj.setCheckIntervalForStats(((Number)member.getValue()).longValue()); + } + break; + case "checkIntervalForStatsTimeUnit": + if (member.getValue() instanceof String) { + obj.setCheckIntervalForStatsTimeUnit(java.util.concurrent.TimeUnit.valueOf((String)member.getValue())); } break; case "peakOutboundGlobalBandwidth": @@ -57,6 +55,8 @@ static void fromJson(Iterable> json, Traffic obj.setPeakOutboundGlobalBandwidth(((Number)member.getValue()).longValue()); } break; + case "maxDelayToWaitTimeUnit": + break; } } } @@ -66,16 +66,16 @@ static void toJson(TrafficShapingOptions obj, JsonObject json) { } static void toJson(TrafficShapingOptions obj, java.util.Map json) { + json.put("inboundGlobalBandwidth", obj.getInboundGlobalBandwidth()); + json.put("outboundGlobalBandwidth", obj.getOutboundGlobalBandwidth()); + json.put("maxDelayToWait", obj.getMaxDelayToWait()); json.put("checkIntervalForStats", obj.getCheckIntervalForStats()); if (obj.getCheckIntervalForStatsTimeUnit() != null) { json.put("checkIntervalForStatsTimeUnit", obj.getCheckIntervalForStatsTimeUnit().name()); } - json.put("inboundGlobalBandwidth", obj.getInboundGlobalBandwidth()); - json.put("maxDelayToWait", obj.getMaxDelayToWait()); + json.put("peakOutboundGlobalBandwidth", obj.getPeakOutboundGlobalBandwidth()); if (obj.getMaxDelayToWaitTimeUnit() != null) { json.put("maxDelayToWaitTimeUnit", obj.getMaxDelayToWaitTimeUnit().name()); } - json.put("outboundGlobalBandwidth", obj.getOutboundGlobalBandwidth()); - json.put("peakOutboundGlobalBandwidth", obj.getPeakOutboundGlobalBandwidth()); } } diff --git a/src/main/java/docoverride/dns/Examples.java b/src/main/java/docoverride/dns/Examples.java deleted file mode 100644 index 6f0a5253bb6..00000000000 --- a/src/main/java/docoverride/dns/Examples.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package docoverride.dns; - -import io.vertx.core.Vertx; -import io.vertx.core.dns.DnsClient; -import io.vertx.core.dns.DnsException; -import io.vertx.core.dns.DnsResponseCode; -import io.vertx.docgen.Source; - -/** - * @author Julien Viet - */ -@Source -public class Examples { - - public void example16(Vertx vertx) { - DnsClient client = vertx.createDnsClient(53, "10.0.0.1"); - client - .lookup("nonexisting.vert.xio") - .onComplete(ar -> { - if (ar.succeeded()) { - String record = ar.result(); - System.out.println(record); - } else { - Throwable cause = ar.cause(); - if (cause instanceof DnsException) { - DnsException exception = (DnsException) cause; - DnsResponseCode code = exception.code(); - // ... - } else { - System.out.println("Failed to resolve entry" + ar.cause()); - } - } - }); - } -} diff --git a/src/main/java/docoverride/eventbus/Examples.java b/src/main/java/docoverride/eventbus/Examples.java deleted file mode 100644 index deaaa3af77c..00000000000 --- a/src/main/java/docoverride/eventbus/Examples.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package docoverride.eventbus; - -import io.vertx.core.eventbus.DeliveryOptions; -import io.vertx.core.eventbus.EventBus; -import io.vertx.core.eventbus.MessageCodec; -import io.vertx.docgen.Source; - -/** - * @author Julien Viet - */ -@Source -public class Examples { - - public void example10(EventBus eventBus, MessageCodec myCodec) { - - eventBus.registerCodec(myCodec); - - DeliveryOptions options = new DeliveryOptions().setCodecName(myCodec.name()); - - eventBus.send("orders", new MyPOJO(), options); - } - - public void example11(EventBus eventBus, MessageCodec myCodec) { - - eventBus.registerDefaultCodec(MyPOJO.class, myCodec); - - eventBus.send("orders", new MyPOJO()); - } - - public void headers(EventBus eventBus) { - DeliveryOptions options = new DeliveryOptions(); - options.addHeader("some-header", "some-value"); - eventBus.send("news.uk.sport", "Yay! Someone kicked a ball", options); - } - - class MyPOJO { - - } - -} diff --git a/src/main/java/examples/BufferExamples.java b/src/main/java/examples/BufferExamples.java index 214eee95a86..34c58061eb0 100644 --- a/src/main/java/examples/BufferExamples.java +++ b/src/main/java/examples/BufferExamples.java @@ -27,6 +27,11 @@ public void example2() { Buffer buff = Buffer.buffer("some string"); } + public void example4() { + byte[] bytes = new byte[] {1, 3, 5}; + Buffer buff = Buffer.buffer(bytes); + } + public void example3() { Buffer buff = Buffer.buffer("some string", "UTF-16"); } diff --git a/src/main/java/examples/CoreExamples.java b/src/main/java/examples/CoreExamples.java index ed697b5e31f..a1377fede91 100644 --- a/src/main/java/examples/CoreExamples.java +++ b/src/main/java/examples/CoreExamples.java @@ -23,6 +23,8 @@ import io.vertx.core.net.NetClient; import io.vertx.core.net.NetServer; import io.vertx.core.net.SocketAddress; +import io.vertx.core.spi.VertxMetricsFactory; +import io.vertx.core.spi.cluster.ClusterManager; import java.util.Arrays; import java.util.concurrent.TimeUnit; @@ -67,10 +69,9 @@ public void example6(HttpServer server) { } public void example7(Vertx vertx) { - vertx.executeBlocking(promise -> { + vertx.executeBlocking(() -> { // Call some blocking API that takes a significant amount of time to return - String result = someAPI.blockingMethod("hello"); - promise.complete(result); + return someAPI.blockingMethod("hello"); }).onComplete(res -> { System.out.println("The result is: " + res.result()); }); @@ -78,10 +79,9 @@ public void example7(Vertx vertx) { public void workerExecutor1(Vertx vertx) { WorkerExecutor executor = vertx.createSharedWorkerExecutor("my-worker-pool"); - executor.executeBlocking(promise -> { + executor.executeBlocking(() -> { // Call some blocking API that takes a significant amount of time to return - String result = someAPI.blockingMethod("hello"); - promise.complete(result); + return someAPI.blockingMethod("hello"); }).onComplete(res -> { System.out.println("The result is: " + res.result()); }); @@ -111,6 +111,20 @@ String blockingMethod(String str) { } } + public void vertxBuilder(VertxOptions options, VertxMetricsFactory metricsFactory) { + Vertx vertx = Vertx.builder() + .with(options) + .withMetrics(metricsFactory) + .build(); + } + + public void clusteredVertxBuilder(VertxOptions options, ClusterManager clusterManager) { + Future vertx = Vertx.builder() + .with(options) + .withClusterManager(clusterManager) + .buildClustered(); + } + public void exampleFuture1(Vertx vertx, Handler requestHandler) { FileSystem fs = vertx.fileSystem(); @@ -220,7 +234,12 @@ public void exampleFutureJoin2(Future future1, Future future2, Future f } public void example7_1(Vertx vertx) { - DeploymentOptions options = new DeploymentOptions().setWorker(true); + DeploymentOptions options = new DeploymentOptions().setThreadingModel(ThreadingModel.WORKER); + vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle", options); + } + + public void example7_2(Vertx vertx) { + DeploymentOptions options = new DeploymentOptions().setThreadingModel(ThreadingModel.VIRTUAL_THREAD); vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle", options); } diff --git a/src/main/java/examples/DNSExamples.java b/src/main/java/examples/DNSExamples.java index 570cd0e6bd7..53922df8ad8 100644 --- a/src/main/java/examples/DNSExamples.java +++ b/src/main/java/examples/DNSExamples.java @@ -12,10 +12,7 @@ package examples; import io.vertx.core.Vertx; -import io.vertx.core.dns.DnsClient; -import io.vertx.core.dns.DnsClientOptions; -import io.vertx.core.dns.MxRecord; -import io.vertx.core.dns.SrvRecord; +import io.vertx.core.dns.*; import java.util.List; @@ -240,4 +237,25 @@ public void example15(Vertx vertx) { } }); } + + public void example16(Vertx vertx) { + DnsClient client = vertx.createDnsClient(53, "10.0.0.1"); + client + .lookup("nonexisting.vert.xio") + .onComplete(ar -> { + if (ar.succeeded()) { + String record = ar.result(); + System.out.println(record); + } else { + Throwable cause = ar.cause(); + if (cause instanceof DnsException) { + DnsException exception = (DnsException) cause; + DnsResponseCode code = exception.code(); + // ... + } else { + System.out.println("Failed to resolve entry" + ar.cause()); + } + } + }); + } } diff --git a/src/main/java/examples/EventBusExamples.java b/src/main/java/examples/EventBusExamples.java index 8beffc8bfac..b4fa8054a08 100644 --- a/src/main/java/examples/EventBusExamples.java +++ b/src/main/java/examples/EventBusExamples.java @@ -13,9 +13,7 @@ import io.vertx.core.Vertx; import io.vertx.core.VertxOptions; -import io.vertx.core.eventbus.EventBus; -import io.vertx.core.eventbus.EventBusOptions; -import io.vertx.core.eventbus.MessageConsumer; +import io.vertx.core.eventbus.*; import io.vertx.core.http.ClientAuth; import io.vertx.core.net.JksOptions; @@ -112,8 +110,8 @@ public void example13() { VertxOptions options = new VertxOptions() .setEventBusOptions(new EventBusOptions() .setSsl(true) - .setKeyStoreOptions(new JksOptions().setPath("keystore.jks").setPassword("wibble")) - .setTrustStoreOptions(new JksOptions().setPath("keystore.jks").setPassword("wibble")) + .setKeyCertOptions(new JksOptions().setPath("keystore.jks").setPassword("wibble")) + .setTrustOptions(new JksOptions().setPath("keystore.jks").setPassword("wibble")) .setClientAuth(ClientAuth.REQUIRED) ); @@ -150,5 +148,29 @@ public void example14() { }); } + public void example10(EventBus eventBus, MessageCodec myCodec) { + eventBus.registerCodec(myCodec); + + DeliveryOptions options = new DeliveryOptions().setCodecName(myCodec.name()); + + eventBus.send("orders", new MyPOJO(), options); + } + + public void example11(EventBus eventBus, MessageCodec myCodec) { + + eventBus.registerDefaultCodec(MyPOJO.class, myCodec); + + eventBus.send("orders", new MyPOJO()); + } + + public void headers(EventBus eventBus) { + DeliveryOptions options = new DeliveryOptions(); + options.addHeader("some-header", "some-value"); + eventBus.send("news.uk.sport", "Yay! Someone kicked a ball", options); + } + + class MyPOJO { + + } } diff --git a/src/main/java/examples/FileSystemExamples.java b/src/main/java/examples/FileSystemExamples.java index cfd7b16bbee..92d668075cb 100644 --- a/src/main/java/examples/FileSystemExamples.java +++ b/src/main/java/examples/FileSystemExamples.java @@ -165,7 +165,7 @@ public void asyncFilePipe(Vertx vertx) { .open("target/classes/les_miserables.txt", new OpenOptions()) .compose(file -> file .pipeTo(output) - .eventually(v -> file.close())) + .eventually(() -> file.close())) .onComplete(result -> { if (result.succeeded()) { System.out.println("Copy done"); diff --git a/src/main/java/examples/HTTP2Examples.java b/src/main/java/examples/HTTP2Examples.java index fc10ec876e0..34527cd1226 100644 --- a/src/main/java/examples/HTTP2Examples.java +++ b/src/main/java/examples/HTTP2Examples.java @@ -13,19 +13,7 @@ import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; -import io.vertx.core.http.Http2Settings; -import io.vertx.core.http.HttpClient; -import io.vertx.core.http.HttpClientOptions; -import io.vertx.core.http.HttpClientRequest; -import io.vertx.core.http.HttpClientResponse; -import io.vertx.core.http.HttpConnection; -import io.vertx.core.http.HttpMethod; -import io.vertx.core.http.HttpServer; -import io.vertx.core.http.HttpServerOptions; -import io.vertx.core.http.HttpServerRequest; -import io.vertx.core.http.HttpServerResponse; -import io.vertx.core.http.HttpVersion; -import io.vertx.core.http.StreamResetException; +import io.vertx.core.http.*; import io.vertx.core.net.JksOptions; /** @@ -37,7 +25,7 @@ public void example0(Vertx vertx) { HttpServerOptions options = new HttpServerOptions() .setUseAlpn(true) .setSsl(true) - .setKeyStoreOptions(new JksOptions().setPath("/path/to/my/keystore")); + .setKeyCertOptions(new JksOptions().setPath("/path/to/my/keystore")); HttpServer server = vertx.createHttpServer(options); } @@ -221,10 +209,14 @@ public void example18(HttpClientRequest request) { HttpConnection connection = request.connection(); } - public void example19(HttpClient client) { - client.connectionHandler(connection -> { - System.out.println("Connected to the server"); - }); + public void example19(Vertx vertx, HttpClientOptions options) { + vertx + .httpClientBuilder() + .with(options) + .withConnectHandler(connection -> { + System.out.println("Connected to the server"); + }) + .build(); } public void example20(HttpConnection connection) { @@ -284,11 +276,10 @@ public void example28(HttpConnection connection) { public void useMaxStreams(Vertx vertx) { - HttpClientOptions clientOptions = new HttpClientOptions(). - setHttp2MultiplexingLimit(10). - setHttp2MaxPoolSize(3); - // Uses up to 3 connections and up to 10 streams per connection - HttpClient client = vertx.createHttpClient(clientOptions); + HttpClient client = vertx.createHttpClient( + new HttpClientOptions().setHttp2MultiplexingLimit(10), + new PoolOptions().setHttp2MaxSize(3) + ); } } diff --git a/src/main/java/examples/HTTPExamples.java b/src/main/java/examples/HTTPExamples.java index c740a234b3a..ee9ab5e3702 100644 --- a/src/main/java/examples/HTTPExamples.java +++ b/src/main/java/examples/HTTPExamples.java @@ -27,6 +27,7 @@ import io.vertx.core.file.OpenOptions; import io.vertx.core.http.*; import io.vertx.core.json.JsonObject; +import io.vertx.core.loadbalancing.LoadBalancer; import io.vertx.core.net.NetSocket; import io.vertx.core.net.ProxyOptions; import io.vertx.core.net.ProxyType; @@ -329,11 +330,24 @@ public void example29(Vertx vertx) { HttpClient client = vertx.createHttpClient(options); } + public void examplePoolConfiguration(Vertx vertx) { + PoolOptions options = new PoolOptions().setHttp1MaxSize(10); + HttpClient client = vertx.createHttpClient(options); + } + public void exampleClientLogging(Vertx vertx) { HttpClientOptions options = new HttpClientOptions().setLogActivity(true); HttpClient client = vertx.createHttpClient(options); } + public void exampleClientBuilder01(Vertx vertx, HttpClientOptions options) { + // Pretty much like vertx.createHttpClient(options) + HttpClient build = vertx + .httpClientBuilder() + .with(options) + .build(); + } + public void example30(HttpClient client) { client .request(HttpMethod.GET, 8080, "myserver.mycompany.com", "/some-uri") @@ -545,6 +559,36 @@ public void example41(HttpClientRequest request) { request.end(); } + public void clientIdleTimeout(HttpClient client, int port, String host, String uri, int timeoutMS) { + Future fut = client + .request(new RequestOptions() + .setHost(host) + .setPort(port) + .setURI(uri) + .setIdleTimeout(timeoutMS)) + .compose(request -> request.send().compose(HttpClientResponse::body)); + } + + public void clientConnectTimeout(HttpClient client, int port, String host, String uri, int timeoutMS) { + Future fut = client + .request(new RequestOptions() + .setHost(host) + .setPort(port) + .setURI(uri) + .setConnectTimeout(timeoutMS)) + .compose(request -> request.send().compose(HttpClientResponse::body)); + } + + public void clientTimeout(HttpClient client, int port, String host, String uri, int timeoutMS) { + Future fut = client + .request(new RequestOptions() + .setHost(host) + .setPort(port) + .setURI(uri) + .setTimeout(timeoutMS)) + .compose(request -> request.send().compose(HttpClientResponse::body)); + } + public void useRequestAsStream(HttpClientRequest request) { request.setChunked(true); @@ -799,23 +843,24 @@ private String resolveURI(String base, String uriRef) { throw new UnsupportedOperationException(); } - public void exampleFollowRedirect03(HttpClient client) { + public void exampleFollowRedirect03(Vertx vertx) { + HttpClient client = vertx.httpClientBuilder() + .withRedirectHandler(response -> { - client.redirectHandler(response -> { + // Only follow 301 code + if (response.statusCode() == 301 && response.getHeader("Location") != null) { - // Only follow 301 code - if (response.statusCode() == 301 && response.getHeader("Location") != null) { + // Compute the redirect URI + String absoluteURI = resolveURI(response.request().absoluteURI(), response.getHeader("Location")); - // Compute the redirect URI - String absoluteURI = resolveURI(response.request().absoluteURI(), response.getHeader("Location")); - - // Create a new ready to use request that the client will use - return Future.succeededFuture(new RequestOptions().setAbsoluteURI(absoluteURI)); - } + // Create a new ready to use request that the client will use + return Future.succeededFuture(new RequestOptions().setAbsoluteURI(absoluteURI)); + } - // We don't redirect - return null; - }); + // We don't redirect + return null; + }) + .build(); } public void example50(HttpClient client) { @@ -965,25 +1010,47 @@ public void example53(HttpServer server) { }); } - public void example54(HttpClient client) { + + public void example54(Vertx vertx) { + WebSocketClient client = vertx.createWebSocketClient(); + client - .webSocket("/some-uri") + .connect(80, "example.com", "/some-uri") .onComplete(res -> { if (res.succeeded()) { WebSocket ws = res.result(); + ws.textMessageHandler(msg -> { + // Handle msg + }); System.out.println("Connected!"); } }); } - public void exampleWebSocketDisableOriginHeader(HttpClient client, String host, int port, String requestUri) { + public void example54_bis(Vertx vertx) { + WebSocketClient client = vertx.createWebSocketClient(); + + client + .webSocket() + .textMessageHandler(msg -> { + // Handle msg + }) + .connect(80, "example.com", "/some-uri") + .onComplete(res -> { + if (res.succeeded()) { + WebSocket ws = res.result(); + } + }); + } + + public void exampleWebSocketDisableOriginHeader(WebSocketClient client, String host, int port, String requestUri) { WebSocketConnectOptions options = new WebSocketConnectOptions() .setHost(host) .setPort(port) .setURI(requestUri) .setAllowOriginHeader(false); client - .webSocket(options) + .connect(options) .onComplete(res -> { if (res.succeeded()) { WebSocket ws = res.result(); @@ -992,14 +1059,14 @@ public void exampleWebSocketDisableOriginHeader(HttpClient client, String host, }); } - public void exampleWebSocketSetOriginHeader(HttpClient client, String host, int port, String requestUri, String origin) { + public void exampleWebSocketSetOriginHeader(WebSocketClient client, String host, int port, String requestUri, String origin) { WebSocketConnectOptions options = new WebSocketConnectOptions() .setHost(host) .setPort(port) .setURI(requestUri) .addHeader(HttpHeaders.ORIGIN, origin); client - .webSocket(options) + .connect(options) .onComplete(res -> { if (res.succeeded()) { WebSocket ws = res.result(); @@ -1242,8 +1309,15 @@ public static void httpClientSharing3(Vertx vertx) { @Override public void start() { // The client creates and use two event-loops for 4 instances - client = vertx.createHttpClient(new HttpClientOptions().setPoolEventLoopSize(2).setShared(true).setName("my-client")); + client = vertx.createHttpClient(new HttpClientOptions().setShared(true).setName("my-client"), new PoolOptions().setEventLoopSize(2)); } }, new DeploymentOptions().setInstances(4)); } + + public static void httpClientSideLoadBalancing(Vertx vertx) { + HttpClient client = vertx + .httpClientBuilder() + .withLoadBalancer(LoadBalancer.ROUND_ROBIN) + .build(); + } } diff --git a/src/main/java/docoverride/json/Examples.java b/src/main/java/examples/JsonExamples.java similarity index 95% rename from src/main/java/docoverride/json/Examples.java rename to src/main/java/examples/JsonExamples.java index e52cff5f6d0..3b7d74aaf25 100644 --- a/src/main/java/docoverride/json/Examples.java +++ b/src/main/java/examples/JsonExamples.java @@ -9,16 +9,14 @@ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ -package docoverride.json; +package examples; import io.vertx.core.http.HttpServerRequest; import io.vertx.core.json.Json; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; -import io.vertx.core.json.pointer.JsonPointer; import io.vertx.docgen.Source; -import java.net.URI; import java.util.HashMap; import java.util.Map; @@ -26,7 +24,7 @@ * Created by tim on 09/01/15. */ @Source -public class Examples { +public class JsonExamples { public void example0_1() { String jsonString = "{\"foo\":\"bar\"}"; diff --git a/src/main/java/examples/NetExamples.java b/src/main/java/examples/NetExamples.java index fe869c5112a..14be4122a0b 100755 --- a/src/main/java/examples/NetExamples.java +++ b/src/main/java/examples/NetExamples.java @@ -24,6 +24,7 @@ import java.security.cert.CertificateException; import java.util.Arrays; +import java.util.concurrent.TimeUnit; /** * Created by tim on 19/01/15. @@ -128,6 +129,19 @@ public void example9(NetServer server) { }); } + public void shutdownHandler(NetClient client, SocketAddress server) { + client + .connect(server) + .onSuccess(so -> { + so.shutdownHandler(timeout -> { + // Notified when the client is closing + }); + }); + + // A few moments later + client.close(30, TimeUnit.SECONDS); + } + public void example9_1(NetSocket socket) { socket.closeHandler(v -> { @@ -225,7 +239,7 @@ public void exampleNetworkActivityLoggingOnClient(Vertx vertx) { // SSL/TLS server key/cert public void example17(Vertx vertx) { - NetServerOptions options = new NetServerOptions().setSsl(true).setKeyStoreOptions( + NetServerOptions options = new NetServerOptions().setSsl(true).setKeyCertOptions( new JksOptions(). setPath("/path/to/your/server-keystore.jks"). setPassword("password-of-your-keystore") @@ -240,12 +254,12 @@ public void example18(Vertx vertx) { setPassword("password-of-your-keystore"); NetServerOptions options = new NetServerOptions(). setSsl(true). - setKeyStoreOptions(jksOptions); + setKeyCertOptions(jksOptions); NetServer server = vertx.createNetServer(options); } public void example19(Vertx vertx) { - NetServerOptions options = new NetServerOptions().setSsl(true).setPfxKeyCertOptions( + NetServerOptions options = new NetServerOptions().setSsl(true).setKeyCertOptions( new PfxOptions(). setPath("/path/to/your/server-keystore.pfx"). setPassword("password-of-your-keystore") @@ -260,12 +274,12 @@ public void example20(Vertx vertx) { setPassword("password-of-your-keystore"); NetServerOptions options = new NetServerOptions(). setSsl(true). - setPfxKeyCertOptions(pfxOptions); + setKeyCertOptions(pfxOptions); NetServer server = vertx.createNetServer(options); } public void example21(Vertx vertx) { - NetServerOptions options = new NetServerOptions().setSsl(true).setPemKeyCertOptions( + NetServerOptions options = new NetServerOptions().setSsl(true).setKeyCertOptions( new PemKeyCertOptions(). setKeyPath("/path/to/your/server-key.pem"). setCertPath("/path/to/your/server-cert.pem") @@ -281,7 +295,7 @@ public void example22(Vertx vertx) { setCertValue(myCertAsABuffer); NetServerOptions options = new NetServerOptions(). setSsl(true). - setPemKeyCertOptions(pemOptions); + setKeyCertOptions(pemOptions); NetServer server = vertx.createNetServer(options); } @@ -301,7 +315,7 @@ public void example23(Vertx vertx) { NetServerOptions options = new NetServerOptions(). setSsl(true). setClientAuth(ClientAuth.REQUIRED). - setTrustStoreOptions( + setTrustOptions( new JksOptions(). setPath("/path/to/your/truststore.jks"). setPassword("password-of-your-truststore") @@ -314,7 +328,7 @@ public void example24(Vertx vertx) { NetServerOptions options = new NetServerOptions(). setSsl(true). setClientAuth(ClientAuth.REQUIRED). - setTrustStoreOptions( + setTrustOptions( new JksOptions(). setValue(myTrustStoreAsABuffer). setPassword("password-of-your-truststore") @@ -326,7 +340,7 @@ public void example25(Vertx vertx) { NetServerOptions options = new NetServerOptions(). setSsl(true). setClientAuth(ClientAuth.REQUIRED). - setPfxTrustOptions( + setTrustOptions( new PfxOptions(). setPath("/path/to/your/truststore.pfx"). setPassword("password-of-your-truststore") @@ -339,7 +353,7 @@ public void example26(Vertx vertx) { NetServerOptions options = new NetServerOptions(). setSsl(true). setClientAuth(ClientAuth.REQUIRED). - setPfxTrustOptions( + setTrustOptions( new PfxOptions(). setValue(myTrustStoreAsABuffer). setPassword("password-of-your-truststore") @@ -351,7 +365,7 @@ public void example27(Vertx vertx) { NetServerOptions options = new NetServerOptions(). setSsl(true). setClientAuth(ClientAuth.REQUIRED). - setPemTrustOptions( + setTrustOptions( new PemTrustOptions(). addCertPath("/path/to/your/server-ca.pem") ); @@ -363,7 +377,7 @@ public void example28(Vertx vertx) { NetServerOptions options = new NetServerOptions(). setSsl(true). setClientAuth(ClientAuth.REQUIRED). - setPemTrustOptions( + setTrustOptions( new PemTrustOptions(). addCertValue(myCaAsABuffer) ); @@ -384,7 +398,7 @@ public void example29(Vertx vertx) { public void example30(Vertx vertx) { NetClientOptions options = new NetClientOptions(). setSsl(true). - setTrustStoreOptions( + setTrustOptions( new JksOptions(). setPath("/path/to/your/truststore.jks"). setPassword("password-of-your-truststore") @@ -396,7 +410,7 @@ public void example31(Vertx vertx) { Buffer myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/truststore.jks"); NetClientOptions options = new NetClientOptions(). setSsl(true). - setTrustStoreOptions( + setTrustOptions( new JksOptions(). setValue(myTrustStoreAsABuffer). setPassword("password-of-your-truststore") @@ -407,7 +421,7 @@ public void example31(Vertx vertx) { public void example32(Vertx vertx) { NetClientOptions options = new NetClientOptions(). setSsl(true). - setPfxTrustOptions( + setTrustOptions( new PfxOptions(). setPath("/path/to/your/truststore.pfx"). setPassword("password-of-your-truststore") @@ -419,7 +433,7 @@ public void example33(Vertx vertx) { Buffer myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/truststore.pfx"); NetClientOptions options = new NetClientOptions(). setSsl(true). - setPfxTrustOptions( + setTrustOptions( new PfxOptions(). setValue(myTrustStoreAsABuffer). setPassword("password-of-your-truststore") @@ -430,7 +444,7 @@ public void example33(Vertx vertx) { public void example34(Vertx vertx) { NetClientOptions options = new NetClientOptions(). setSsl(true). - setPemTrustOptions( + setTrustOptions( new PemTrustOptions(). addCertPath("/path/to/your/ca-cert.pem") ); @@ -441,7 +455,7 @@ public void example35(Vertx vertx) { Buffer myTrustStoreAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/ca-cert.pem"); NetClientOptions options = new NetClientOptions(). setSsl(true). - setPemTrustOptions( + setTrustOptions( new PemTrustOptions(). addCertValue(myTrustStoreAsABuffer) ); @@ -451,7 +465,7 @@ public void example35(Vertx vertx) { // SSL/TLS client key/cert public void example36(Vertx vertx) { - NetClientOptions options = new NetClientOptions().setSsl(true).setKeyStoreOptions( + NetClientOptions options = new NetClientOptions().setSsl(true).setKeyCertOptions( new JksOptions(). setPath("/path/to/your/client-keystore.jks"). setPassword("password-of-your-keystore") @@ -466,12 +480,12 @@ public void example37(Vertx vertx) { setPassword("password-of-your-keystore"); NetClientOptions options = new NetClientOptions(). setSsl(true). - setKeyStoreOptions(jksOptions); + setKeyCertOptions(jksOptions); NetClient client = vertx.createNetClient(options); } public void example38(Vertx vertx) { - NetClientOptions options = new NetClientOptions().setSsl(true).setPfxKeyCertOptions( + NetClientOptions options = new NetClientOptions().setSsl(true).setKeyCertOptions( new PfxOptions(). setPath("/path/to/your/client-keystore.pfx"). setPassword("password-of-your-keystore") @@ -486,12 +500,12 @@ public void example39(Vertx vertx) { setPassword("password-of-your-keystore"); NetClientOptions options = new NetClientOptions(). setSsl(true). - setPfxKeyCertOptions(pfxOptions); + setKeyCertOptions(pfxOptions); NetClient client = vertx.createNetClient(options); } public void example40(Vertx vertx) { - NetClientOptions options = new NetClientOptions().setSsl(true).setPemKeyCertOptions( + NetClientOptions options = new NetClientOptions().setSsl(true).setKeyCertOptions( new PemKeyCertOptions(). setKeyPath("/path/to/your/client-key.pem"). setCertPath("/path/to/your/client-cert.pem") @@ -507,12 +521,12 @@ public void example41(Vertx vertx) { setCertValue(myCertAsABuffer); NetClientOptions options = new NetClientOptions(). setSsl(true). - setPemKeyCertOptions(pemOptions); + setKeyCertOptions(pemOptions); NetClient client = vertx.createNetClient(options); } public void updateSSLOptions(HttpServer server) { - Future fut = server.updateSSLOptions(new SSLOptions() + Future fut = server.updateSSLOptions(new ServerSSLOptions() .setKeyCertOptions( new JksOptions() .setPath("/path/to/your/server-keystore.jks"). @@ -522,7 +536,7 @@ public void updateSSLOptions(HttpServer server) { public void example42(Vertx vertx, JksOptions trustOptions) { NetClientOptions options = new NetClientOptions(). setSsl(true). - setTrustStoreOptions(trustOptions). + setTrustOptions(trustOptions). addCrlPath("/path/to/your/crl.pem"); NetClient client = vertx.createNetClient(options); } @@ -531,7 +545,7 @@ public void example43(Vertx vertx, JksOptions trustOptions) { Buffer myCrlAsABuffer = vertx.fileSystem().readFileBlocking("/path/to/your/crl.pem"); NetClientOptions options = new NetClientOptions(). setSsl(true). - setTrustStoreOptions(trustOptions). + setTrustOptions(trustOptions). addCrlValue(myCrlAsABuffer); NetClient client = vertx.createNetClient(options); } @@ -539,7 +553,7 @@ public void example43(Vertx vertx, JksOptions trustOptions) { public void example44(Vertx vertx, JksOptions keyStoreOptions) { NetServerOptions options = new NetServerOptions(). setSsl(true). - setKeyStoreOptions(keyStoreOptions). + setKeyCertOptions(keyStoreOptions). addEnabledCipherSuite("ECDHE-RSA-AES128-GCM-SHA256"). addEnabledCipherSuite("ECDHE-ECDSA-AES128-GCM-SHA256"). addEnabledCipherSuite("ECDHE-RSA-AES256-GCM-SHA384"). @@ -550,7 +564,7 @@ public void example44(Vertx vertx, JksOptions keyStoreOptions) { public void addEnabledTLSPrococol(Vertx vertx, JksOptions keyStoreOptions) { NetServerOptions options = new NetServerOptions(). setSsl(true). - setKeyStoreOptions(keyStoreOptions). + setKeyCertOptions(keyStoreOptions). addEnabledSecureTransportProtocol("TLSv1.1"); NetServer server = vertx.createNetServer(options); } @@ -558,7 +572,7 @@ public void addEnabledTLSPrococol(Vertx vertx, JksOptions keyStoreOptions) { public void removeEnabledTLSPrococol(Vertx vertx, JksOptions keyStoreOptions) { NetServerOptions options = new NetServerOptions(). setSsl(true). - setKeyStoreOptions(keyStoreOptions). + setKeyCertOptions(keyStoreOptions). removeEnabledSecureTransportProtocol("TLSv1.2"); NetServer server = vertx.createNetServer(options); } @@ -568,19 +582,19 @@ public void exampleSSLEngine(Vertx vertx, JksOptions keyStoreOptions) { // Use JDK SSL engine NetServerOptions options = new NetServerOptions(). setSsl(true). - setKeyStoreOptions(keyStoreOptions); + setKeyCertOptions(keyStoreOptions); // Use JDK SSL engine explicitly options = new NetServerOptions(). setSsl(true). - setKeyStoreOptions(keyStoreOptions). - setJdkSslEngineOptions(new JdkSSLEngineOptions()); + setKeyCertOptions(keyStoreOptions). + setSslEngineOptions(new JdkSSLEngineOptions()); // Use OpenSSL engine options = new NetServerOptions(). setSsl(true). - setKeyStoreOptions(keyStoreOptions). - setOpenSslEngineOptions(new OpenSSLEngineOptions()); + setKeyCertOptions(keyStoreOptions). + setSslEngineOptions(new OpenSSLEngineOptions()); } public void example46(Vertx vertx, JksOptions keyStoreOptions) { @@ -671,7 +685,7 @@ public void configureSNIServer(Vertx vertx) { JksOptions keyCertOptions = new JksOptions().setPath("keystore.jks").setPassword("wibble"); NetServer netServer = vertx.createNetServer(new NetServerOptions() - .setKeyStoreOptions(keyCertOptions) + .setKeyCertOptions(keyCertOptions) .setSsl(true) .setSni(true) ); @@ -684,7 +698,7 @@ public void configureSNIServerWithPems(Vertx vertx) { ); NetServer netServer = vertx.createNetServer(new NetServerOptions() - .setPemKeyCertOptions(keyCertOptions) + .setKeyCertOptions(keyCertOptions) .setSsl(true) .setSni(true) ); @@ -693,7 +707,7 @@ public void configureSNIServerWithPems(Vertx vertx) { public void useSNIInClient(Vertx vertx, JksOptions trustOptions) { NetClient client = vertx.createNetClient(new NetClientOptions() - .setTrustStoreOptions(trustOptions) + .setTrustOptions(trustOptions) .setSsl(true) ); @@ -711,6 +725,17 @@ public void useSNIInClient(Vertx vertx, JksOptions trustOptions) { } public void configureTrafficShapingForNetServer(Vertx vertx) { + NetServerOptions options = new NetServerOptions() + .setHost("localhost") + .setPort(1234) + .setTrafficShapingOptions(new TrafficShapingOptions() + .setInboundGlobalBandwidth(64 * 1024) + .setOutboundGlobalBandwidth(128 * 1024)); + + NetServer server = vertx.createNetServer(options); + } + + public void dynamicallyUpdateTrafficShapingForNetServer(Vertx vertx) { NetServerOptions options = new NetServerOptions() .setHost("localhost") .setPort(1234) @@ -718,16 +743,41 @@ public void configureTrafficShapingForNetServer(Vertx vertx) { .setInboundGlobalBandwidth(64 * 1024) .setOutboundGlobalBandwidth(128 * 1024)); NetServer server = vertx.createNetServer(options); + TrafficShapingOptions update = new TrafficShapingOptions() + .setInboundGlobalBandwidth(2 * 64 * 1024) // twice + .setOutboundGlobalBandwidth(128 * 1024); // unchanged + server + .listen(1234, "localhost") + // wait until traffic shaping handler is created for updates + .onSuccess(v -> server.updateTrafficShapingOptions(update)); } public void configureTrafficShapingForHttpServer(Vertx vertx) { + HttpServerOptions options = new HttpServerOptions() + .setHost("localhost") + .setPort(1234) + .setTrafficShapingOptions(new TrafficShapingOptions() + .setInboundGlobalBandwidth(64 * 1024) + .setOutboundGlobalBandwidth(128 * 1024)); + + HttpServer server = vertx.createHttpServer(options); + } + + + public void dynamicallyUpdateTrafficShapingForHttpServer(Vertx vertx) { HttpServerOptions options = new HttpServerOptions() .setHost("localhost") .setPort(1234) .setTrafficShapingOptions(new TrafficShapingOptions() .setInboundGlobalBandwidth(64 * 1024) .setOutboundGlobalBandwidth(128 * 1024)); - HttpServer server = vertx.createHttpServer(options); + TrafficShapingOptions update = new TrafficShapingOptions() + .setInboundGlobalBandwidth(2 * 64 * 1024) // twice + .setOutboundGlobalBandwidth(128 * 1024); // unchanged + server + .listen(1234, "localhost") + // wait until traffic shaping handler is created for updates + .onSuccess(v -> server.updateTrafficShapingOptions(update)); } } diff --git a/src/main/java/examples/SharedDataExamples.java b/src/main/java/examples/SharedDataExamples.java index c67e28dac96..0095dd8e3ce 100644 --- a/src/main/java/examples/SharedDataExamples.java +++ b/src/main/java/examples/SharedDataExamples.java @@ -11,6 +11,7 @@ package examples; +import io.vertx.core.Future; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; import io.vertx.core.shareddata.*; @@ -116,6 +117,21 @@ public void lock(Vertx vertx) { }); } + private Future getAsyncString() { + throw new UnsupportedOperationException(); + } + + public void withLock(Vertx vertx) { + SharedData sharedData = vertx.sharedData(); + + Future res = sharedData.withLock("mylock", () -> { + // Obtained the lock! + Future future = getAsyncString(); + // It will be released upon completion of this future + return future; + }); + } + public void lockWithTimeout(Vertx vertx) { SharedData sharedData = vertx.sharedData(); diff --git a/src/main/java/examples/StreamsExamples.java b/src/main/java/examples/StreamsExamples.java index 4911e08f565..1d13fb3a82d 100644 --- a/src/main/java/examples/StreamsExamples.java +++ b/src/main/java/examples/StreamsExamples.java @@ -17,14 +17,9 @@ import io.vertx.core.file.AsyncFile; import io.vertx.core.file.FileSystem; import io.vertx.core.file.OpenOptions; -import io.vertx.core.http.HttpServer; -import io.vertx.core.http.HttpServerOptions; import io.vertx.core.net.NetServer; import io.vertx.core.net.NetServerOptions; -import io.vertx.core.net.NetSocket; import io.vertx.core.streams.Pipe; -import io.vertx.core.streams.Pump; -import io.vertx.core.streams.ReadStream; /** * @author Julien Viet diff --git a/src/main/java/examples/VirtualThreadExamples.java b/src/main/java/examples/VirtualThreadExamples.java new file mode 100644 index 00000000000..cd0b4b77feb --- /dev/null +++ b/src/main/java/examples/VirtualThreadExamples.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package examples; + +import io.vertx.core.*; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.*; +import io.vertx.docgen.Source; + +import java.util.concurrent.CompletionStage; + +@Source +public class VirtualThreadExamples { + + public void gettingStarted(Vertx vertx) { + + AbstractVerticle verticle = new AbstractVerticle() { + @Override + public void start() { + HttpClient client = vertx.createHttpClient(); + HttpClientRequest req = client.request( + HttpMethod.GET, + 8080, + "localhost", + "/").await(); + HttpClientResponse resp = req.send().await(); + int status = resp.statusCode(); + Buffer body = resp.body().await(); + } + }; + + // Run the verticle a on virtual thread + vertx.deployVerticle(verticle, new DeploymentOptions().setThreadingModel(ThreadingModel.VIRTUAL_THREAD)); + } + + private int counter; + + public void fieldVisibility1() { + int value = counter; + value += getRemoteValue().await(); + // the counter value might have changed + counter = value; + } + + public void fieldVisibility2() { + counter += getRemoteValue().await(); + } + + private Future callRemoteService() { + return null; + } + + private Future getRemoteValue() { + return null; + } + + public void deployVerticle(Vertx vertx, int port) { + vertx.deployVerticle(() -> new AbstractVerticle() { + HttpServer server; + @Override + public void start() { + server = vertx + .createHttpServer() + .requestHandler(req -> { + Buffer res; + try { + res = callRemoteService().await(); + } catch (Exception e) { + req.response().setStatusCode(500).end(); + return; + } + req.response().end(res); + }); + server.listen(port).await(); + } + }, new DeploymentOptions() + .setThreadingModel(ThreadingModel.VIRTUAL_THREAD)); + } + + public void awaitingFutures1(HttpClientResponse response) { + Buffer body = response.body().await(); + } + + public void awaitingFutures2(HttpClientResponse response, CompletionStage completionStage) { + Buffer body = Future.fromCompletionStage(completionStage).await(); + } + + private Future getRemoteString() { + return null; + } + + public void awaitingMultipleFutures() { + Future f1 = getRemoteString(); + Future f2 = getRemoteValue(); + CompositeFuture res = Future.all(f1, f2).await(); + String v1 = res.resultAt(0); + Integer v2 = res.resultAt(1); + } + + public void threadLocalSupport1(String userId, HttpClient client) { + ThreadLocal local = new ThreadLocal(); + local.set(userId); + HttpClientRequest req = client.request(HttpMethod.GET, 8080, "localhost", "/").await(); + HttpClientResponse resp = req.send().await(); + // Thread local remains the same since it's the same virtual thread + } +} diff --git a/src/main/java/examples/cli/AnnotatedCli.java b/src/main/java/examples/cli/AnnotatedCli.java deleted file mode 100644 index b45d5f7c86f..00000000000 --- a/src/main/java/examples/cli/AnnotatedCli.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package examples.cli; - -import io.vertx.core.cli.annotations.*; - -/** - * An example of annotated cli. - * @author Clement Escoffier - */ -// tag::content[] -@Name("some-name") -@Summary("some short summary.") -@Description("some long description") -public class AnnotatedCli { - - private boolean flag; - private String name; - private String arg; - - @Option(shortName = "f", flag = true) - public void setFlag(boolean flag) { - this.flag = flag; - } - - @Option(longName = "name") - public void setName(String name) { - this.name = name; - } - - @Argument(index = 0) - public void setArg(String arg) { - this.arg = arg; - } -} -// end::content[] diff --git a/src/main/java/examples/cli/CLIExamples.java b/src/main/java/examples/cli/CLIExamples.java deleted file mode 100644 index a343cb4a8dd..00000000000 --- a/src/main/java/examples/cli/CLIExamples.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package examples.cli; - -import io.vertx.core.cli.Argument; -import io.vertx.core.cli.CLI; -import io.vertx.core.cli.CommandLine; -import io.vertx.core.cli.Option; - -import java.io.PrintStream; -import java.util.Collections; -import java.util.List; - -/** - * @author Clement Escoffier - */ -public class CLIExamples { - - public void example1() { - CLI cli = CLI.create("copy") - .setSummary("A command line interface to copy files.") - .addOption(new Option() - .setLongName("directory") - .setShortName("R") - .setDescription("enables directory support") - .setFlag(true)) - .addArgument(new Argument() - .setIndex(0) - .setDescription("The source") - .setArgName("source")) - .addArgument(new Argument() - .setIndex(1) - .setDescription("The destination") - .setArgName("target")); - } - - public void example2() { - CLI cli = CLI.create("some-name") - .setSummary("A command line interface illustrating the options valuation.") - .addOption(new Option() - .setLongName("flag").setShortName("f").setFlag(true).setDescription("a flag")) - .addOption(new Option() - .setLongName("single").setShortName("s").setDescription("a single-valued option")) - .addOption(new Option() - .setLongName("multiple").setShortName("m").setMultiValued(true) - .setDescription("a multi-valued option")); - } - - public void example3() { - CLI cli = CLI.create("some-name") - .addOption(new Option() - .setLongName("mandatory") - .setRequired(true) - .setDescription("a mandatory option")); - } - - public void example4() { - CLI cli = CLI.create("some-name") - .addOption(new Option() - .setLongName("optional") - .setDefaultValue("hello") - .setDescription("an optional option with a default value")); - } - - public void example41() { - CLI cli = CLI.create("some-name") - .addOption(new Option() - .setLongName("color") - .setDefaultValue("green") - .addChoice("blue").addChoice("red").addChoice("green") - .setDescription("a color")); - } - - public void example5() { - CLI cli = CLI.create("some-name") - .addArgument(new Argument() - .setIndex(0) - .setDescription("the first argument") - .setArgName("arg1")) - .addArgument(new Argument() - .setIndex(1) - .setDescription("the second argument") - .setArgName("arg2")); - } - - public void example51() { - CLI cli = CLI.create("some-name") - // will have the index 0 - .addArgument(new Argument() - .setDescription("the first argument") - .setArgName("arg1")) - // will have the index 1 - .addArgument(new Argument() - .setDescription("the second argument") - .setArgName("arg2")); - } - - public void example6() { - CLI cli = CLI.create("copy") - .setSummary("A command line interface to copy files.") - .addOption(new Option() - .setLongName("directory") - .setShortName("R") - .setDescription("enables directory support") - .setFlag(true)) - .addArgument(new Argument() - .setIndex(0) - .setDescription("The source") - .setArgName("source")) - .addArgument(new Argument() - .setIndex(0) - .setDescription("The destination") - .setArgName("target")); - - StringBuilder builder = new StringBuilder(); - cli.usage(builder); - } - - public void example7(CLI cli, List userCommandLineArguments) { - CommandLine commandLine = cli.parse(userCommandLineArguments); - } - - public void example8(CLI cli, List userCommandLineArguments) { - CommandLine commandLine = cli.parse(userCommandLineArguments); - String opt = commandLine.getOptionValue("my-option"); - boolean flag = commandLine.isFlagEnabled("my-flag"); - String arg0 = commandLine.getArgumentValue(0); - } - - public void example9(PrintStream stream) { - CLI cli = CLI.create("test") - .addOption( - new Option().setLongName("help").setShortName("h").setFlag(true).setHelp(true)) - .addOption( - new Option().setLongName("mandatory").setRequired(true)); - - CommandLine line = cli.parse(Collections.singletonList("-h")); - - // The parsing does not fail and let you do: - if (!line.isValid() && line.isAskingForHelp()) { - StringBuilder builder = new StringBuilder(); - cli.usage(builder); - stream.print(builder.toString()); - } - } -} diff --git a/src/main/java/examples/cli/MyCommand.java b/src/main/java/examples/cli/MyCommand.java deleted file mode 100644 index c6bbce1fdc1..00000000000 --- a/src/main/java/examples/cli/MyCommand.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package examples.cli; - -// tag::content[] - -import io.vertx.core.cli.CLIException; -import io.vertx.core.cli.annotations.Name; -import io.vertx.core.cli.annotations.Option; -import io.vertx.core.cli.annotations.Summary; -import io.vertx.core.spi.launcher.DefaultCommand; - -@Name("my-command") -@Summary("A simple hello command.") -public class MyCommand extends DefaultCommand { - - private String name; - - @Option(longName = "name", required = true) - public void setName(String n) { - this.name = n; - } - - @Override - public void run() throws CLIException { - System.out.println("Hello " + name); - } -} - -// end::content[] diff --git a/src/main/java/examples/cli/TypedCLIExamples.java b/src/main/java/examples/cli/TypedCLIExamples.java deleted file mode 100644 index feb10c93c73..00000000000 --- a/src/main/java/examples/cli/TypedCLIExamples.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package examples.cli; - -import io.vertx.core.cli.CLI; -import io.vertx.core.cli.CommandLine; -import io.vertx.core.cli.TypedArgument; -import io.vertx.core.cli.TypedOption; -import io.vertx.core.cli.annotations.CLIConfigurator; -import io.vertx.core.cli.converters.Converter; -import io.vertx.docgen.Source; - -import java.io.File; -import java.util.List; - -/** - * @author Clement Escoffier - */ -@Source(translate = false) -public class TypedCLIExamples { - - public void example1() { - CLI cli = CLI.create("copy") - .setSummary("A command line interface to copy files.") - .addOption(new TypedOption() - .setType(Boolean.class) - .setLongName("directory") - .setShortName("R") - .setDescription("enables directory support") - .setFlag(true)) - .addArgument(new TypedArgument() - .setType(File.class) - .setIndex(0) - .setDescription("The source") - .setArgName("source")) - .addArgument(new TypedArgument() - .setType(File.class) - .setIndex(0) - .setDescription("The destination") - .setArgName("target")); - } - - public void example2(CLI cli, List userCommandLineArguments) { - CommandLine commandLine = cli.parse(userCommandLineArguments); - boolean flag = commandLine.getOptionValue("R"); - File source = commandLine.getArgumentValue("source"); - File target = commandLine.getArgumentValue("target"); - } - - public void example3() { - CLI cli = CLI.create("some-name") - .addOption(new TypedOption() - .setType(Person.class) - .setConverter(new PersonConverter()) - .setLongName("person")); - } - - public void example4(List userCommandLineArguments) { - CLI cli = CLI.create(AnnotatedCli.class); - CommandLine commandLine = cli.parse(userCommandLineArguments); - AnnotatedCli instance = new AnnotatedCli(); - CLIConfigurator.inject(commandLine, instance); - } - - private class Person { - - } - - private class PersonConverter implements Converter { - - @Override - public Person fromString(String s) { - return null; - } - } - - -} diff --git a/src/main/java/examples/cli/package-info.java b/src/main/java/examples/cli/package-info.java deleted file mode 100644 index 52a1c953f99..00000000000 --- a/src/main/java/examples/cli/package-info.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -/** - * @author Julien Viet - */ -@Source -package examples.cli; - -import io.vertx.docgen.Source; diff --git a/src/main/java/io/vertx/core/AbstractVerticle.java b/src/main/java/io/vertx/core/AbstractVerticle.java index 6de46988681..6fca2ca66f0 100644 --- a/src/main/java/io/vertx/core/AbstractVerticle.java +++ b/src/main/java/io/vertx/core/AbstractVerticle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -13,6 +13,7 @@ import io.vertx.core.json.JsonObject; +import java.util.Collections; import java.util.List; /** @@ -86,11 +87,12 @@ public JsonObject config() { } /** - * Get the arguments used when deploying the Vert.x process. - * @return the list of arguments + * @return an empty list + * @deprecated As of version 5, Vert.x is no longer tightly coupled to the CLI */ + @Deprecated public List processArgs() { - return context.processArgs(); + return Collections.emptyList(); } /** diff --git a/src/main/java/io/vertx/core/Context.java b/src/main/java/io/vertx/core/Context.java index 31dda087740..083849e15a2 100644 --- a/src/main/java/io/vertx/core/Context.java +++ b/src/main/java/io/vertx/core/Context.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -16,10 +16,11 @@ import io.vertx.codegen.annotations.Nullable; import io.vertx.codegen.annotations.VertxGen; import io.vertx.core.impl.VertxThread; -import io.vertx.core.impl.launcher.VertxCommandLauncher; import io.vertx.core.json.JsonObject; +import java.util.Collections; import java.util.List; +import java.util.concurrent.Callable; /** * The execution context of a {@link io.vertx.core.Handler} execution. @@ -45,10 +46,10 @@ * of the verticle. *

* This means (in the case of a standard verticle) that the verticle code will always be executed with the exact same - * thread, so you don't have to worry about multi-threaded acccess to the verticle state and you can code your application + * thread, so you don't have to worry about multithreaded acccess to the verticle state, and you can code your application * as single threaded. *

- * This class also allows arbitrary data to be {@link #put} and {@link #get} on the context so it can be shared easily + * This class also allows arbitrary data to be {@link #put} and {@link #get} on the context, so it can be shared easily * amongst different handlers of, for example, a verticle instance. *

* This class also provides {@link #runOnContext} which allows an action to be executed asynchronously using the same context. @@ -105,14 +106,10 @@ static boolean isOnVertxThread() { *

* Executes the blocking code in the handler {@code blockingCodeHandler} using a thread from the worker pool. *

- * When the code is complete the handler {@code resultHandler} will be called with the result on the original context - * (e.g. on the original event loop of the caller). + * The returned future will be completed with the result on the original context (i.e. on the original event loop of the caller) + * or failed when the handler throws an exception. *

- * A {@code Future} instance is passed into {@code blockingCodeHandler}. When the blocking code successfully completes, - * the handler should call the {@link Promise#complete} or {@link Promise#complete(Object)} method, or the {@link Promise#fail} - * method if it failed. - *

- * The blocking code should block for a reasonable amount of time (i.e no more than a few seconds). Long blocking operations + * The blocking code should block for a reasonable amount of time (i.e. no more than a few seconds). Long blocking operations * or polling operations (i.e a thread that spin in a loop polling events in a blocking fashion) are precluded. *

* When the blocking operation lasts more than the 10 seconds, a message will be printed on the console by the @@ -128,15 +125,17 @@ static boolean isOnVertxThread() { * @param the type of the result * @return a future completed when the blocking code is complete */ - Future<@Nullable T> executeBlocking(Handler> blockingCodeHandler, boolean ordered); + @GenIgnore(GenIgnore.PERMITTED_TYPE) + Future<@Nullable T> executeBlocking(Callable blockingCodeHandler, boolean ordered); /** - * Invoke {@link #executeBlocking(Handler, boolean)} with order = true. + * Invoke {@link #executeBlocking(Callable, boolean)} with order = true. * @param blockingCodeHandler handler representing the blocking code to run * @param the type of the result * @return a future completed when the blocking code is complete */ - default Future executeBlocking(Handler> blockingCodeHandler) { + @GenIgnore(GenIgnore.PERMITTED_TYPE) + default Future<@Nullable T> executeBlocking(Callable blockingCodeHandler) { return executeBlocking(blockingCodeHandler, true); } @@ -156,34 +155,41 @@ default Future executeBlocking(Handler> blockingCodeHandler) { @Nullable JsonObject config(); /** - * The process args + * @return an empty list + * @deprecated As of version 5, Vert.x is no longer tightly coupled to the CLI */ + @Deprecated default List processArgs() { - return VertxCommandLauncher.getProcessArguments(); + return Collections.emptyList(); } /** * Is the current context an event loop context? *

- * NOTE! when running blocking code using {@link io.vertx.core.Vertx#executeBlocking(Handler, Handler)} from a + * NOTE! when running blocking code using {@link io.vertx.core.Vertx#executeBlocking(Callable)} from a * standard (not worker) verticle, the context will still an event loop context and this {@link this#isEventLoopContext()} * will return true. * - * @return true if false otherwise + * @return {@code true} if the current context is an event-loop context, {@code false} otherwise */ boolean isEventLoopContext(); /** * Is the current context a worker context? *

- * NOTE! when running blocking code using {@link io.vertx.core.Vertx#executeBlocking(Handler, Handler)} from a + * NOTE! when running blocking code using {@link io.vertx.core.Vertx#executeBlocking(Callable)} from a * standard (not worker) verticle, the context will still an event loop context and this {@link this#isWorkerContext()} * will return false. * - * @return true if the current context is a worker context, false otherwise + * @return {@code true} if the current context is a worker context, {@code false} otherwise */ boolean isWorkerContext(); + /** + * @return the context threading model + */ + ThreadingModel threadingModel(); + /** * Get some data from the context. * diff --git a/src/main/java/io/vertx/core/DeploymentOptions.java b/src/main/java/io/vertx/core/DeploymentOptions.java index 04487a72557..4c4c06b9229 100644 --- a/src/main/java/io/vertx/core/DeploymentOptions.java +++ b/src/main/java/io/vertx/core/DeploymentOptions.java @@ -12,11 +12,9 @@ package io.vertx.core; import io.vertx.codegen.annotations.DataObject; -import io.vertx.core.json.JsonArray; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.json.JsonObject; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.TimeUnit; /** @@ -26,32 +24,33 @@ * * @author Tim Fox */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public class DeploymentOptions { + public static final ThreadingModel DEFAULT_MODE = ThreadingModel.EVENT_LOOP; public static final boolean DEFAULT_WORKER = false; public static final boolean DEFAULT_HA = false; public static final int DEFAULT_INSTANCES = 1; private JsonObject config; - private boolean worker; + private ThreadingModel threadingModel; + private boolean ha; + private int instances; + private ClassLoader classLoader; private String workerPoolName; private int workerPoolSize; private long maxWorkerExecuteTime; - private boolean ha; - private int instances; private TimeUnit maxWorkerExecuteTimeUnit; - private ClassLoader classLoader; /** * Default constructor */ public DeploymentOptions() { - this.worker = DEFAULT_WORKER; + this.threadingModel = DEFAULT_MODE; this.config = null; this.ha = DEFAULT_HA; this.instances = DEFAULT_INSTANCES; - this.workerPoolName = null; this.workerPoolSize = VertxOptions.DEFAULT_WORKER_POOL_SIZE; this.maxWorkerExecuteTime = VertxOptions.DEFAULT_MAX_WORKER_EXECUTE_TIME; this.maxWorkerExecuteTimeUnit = VertxOptions.DEFAULT_MAX_WORKER_EXECUTE_TIME_UNIT; @@ -64,12 +63,12 @@ public DeploymentOptions() { */ public DeploymentOptions(DeploymentOptions other) { this.config = other.getConfig() == null ? null : other.getConfig().copy(); - this.worker = other.isWorker(); + this.threadingModel = other.getThreadingModel(); this.ha = other.isHa(); this.instances = other.instances; this.workerPoolName = other.workerPoolName; - setWorkerPoolSize(other.workerPoolSize); - setMaxWorkerExecuteTime(other.maxWorkerExecuteTime); + this.workerPoolSize = other.workerPoolSize; + this.maxWorkerExecuteTime = other.maxWorkerExecuteTime; this.maxWorkerExecuteTimeUnit = other.maxWorkerExecuteTimeUnit; } @@ -83,18 +82,6 @@ public DeploymentOptions(JsonObject json) { DeploymentOptionsConverter.fromJson(json, this); } - /** - * Initialise the fields of this instance from the specified JSON - * - * @param json the JSON - */ - public void fromJson(JsonObject json) { - this.config = json.getJsonObject("config"); - this.worker = json.getBoolean("worker", DEFAULT_WORKER); - this.ha = json.getBoolean("ha", DEFAULT_HA); - this.instances = json.getInteger("instances", DEFAULT_INSTANCES); - } - /** * Get the JSON configuration that will be passed to the verticle(s) when deployed. * @@ -116,22 +103,22 @@ public DeploymentOptions setConfig(JsonObject config) { } /** - * Should the verticle(s) be deployed as a worker verticle? + * Which threading model the verticle(s) should use? * - * @return true if will be deployed as worker, false otherwise + * @return the verticle threading model */ - public boolean isWorker() { - return worker; + public ThreadingModel getThreadingModel() { + return threadingModel; } /** - * Set whether the verticle(s) should be deployed as a worker verticle + * Set the verticle(s) verticle(s) threading model, e.g. a worker or a virtual thread verticle * - * @param worker true for worker, false otherwise + * @param threadingModel the threading model * @return a reference to this, so the API can be used fluently */ - public DeploymentOptions setWorker(boolean worker) { - this.worker = worker; + public DeploymentOptions setThreadingModel(ThreadingModel threadingModel) { + this.threadingModel = threadingModel; return this; } @@ -218,7 +205,7 @@ public int getWorkerPoolSize() { */ public DeploymentOptions setWorkerPoolSize(int workerPoolSize) { if (workerPoolSize < 1) { - throw new IllegalArgumentException("workerPoolSize must be > 0"); + throw new IllegalArgumentException("size must be > 0"); } this.workerPoolSize = workerPoolSize; return this; @@ -252,7 +239,7 @@ public long getMaxWorkerExecuteTime() { */ public DeploymentOptions setMaxWorkerExecuteTime(long maxWorkerExecuteTime) { if (maxWorkerExecuteTime < 1) { - throw new IllegalArgumentException("maxWorkerExecuteTime must be > 0"); + throw new IllegalArgumentException("maxExecuteTime must be > 0"); } this.maxWorkerExecuteTime = maxWorkerExecuteTime; return this; diff --git a/src/main/java/io/vertx/core/Future.java b/src/main/java/io/vertx/core/Future.java index 39fe9affd99..b32f51a329c 100644 --- a/src/main/java/io/vertx/core/Future.java +++ b/src/main/java/io/vertx/core/Future.java @@ -14,6 +14,7 @@ import io.vertx.codegen.annotations.Fluent; import io.vertx.codegen.annotations.GenIgnore; import io.vertx.core.impl.ContextInternal; +import io.vertx.core.impl.Utils; import io.vertx.core.impl.future.CompositeFutureImpl; import io.vertx.core.impl.future.FailedFuture; import io.vertx.core.impl.future.SucceededFuture; @@ -21,7 +22,9 @@ import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; +import java.util.concurrent.TimeUnit; import java.util.function.Function; +import java.util.function.Supplier; /** * Represents the result of an action that may, or may not, have occurred yet. @@ -266,6 +269,26 @@ static Future failedFuture(String failureMessage) { @Fluent Future onComplete(Handler> handler); + /** + * Add handlers to be notified on succeeded result and failed result. + *

+ * WARNING: this is a terminal operation. + * If several {@code handler}s are registered, there is no guarantee that they will be invoked in order of registration. + * + * @param successHandler the handler that will be called with the succeeded result + * @param failureHandler the handler that will be called with the failed result + * @return a reference to this, so it can be used fluently + */ + default Future onComplete(Handler successHandler, Handler failureHandler) { + return onComplete(ar -> { + if (successHandler != null && ar.succeeded()) { + successHandler.handle(ar.result()); + } else if (failureHandler != null && ar.failed()) { + failureHandler.handle(ar.cause()); + } + }); + } + /** * Add a handler to be notified of the succeeded result. *

@@ -277,11 +300,7 @@ static Future failedFuture(String failureMessage) { */ @Fluent default Future onSuccess(Handler handler) { - return onComplete(ar -> { - if (ar.succeeded()) { - handler.handle(ar.result()); - } - }); + return onComplete(handler, null); } /** @@ -295,11 +314,7 @@ default Future onSuccess(Handler handler) { */ @Fluent default Future onFailure(Handler handler) { - return onComplete(ar -> { - if (ar.failed()) { - handler.handle(ar.cause()); - } - }); + return onComplete(null, handler); } /** @@ -417,7 +432,7 @@ default Future recover(Function> mapper) { * @param mapper the function returning the future. * @return the composed future */ - Future eventually(Function> mapper); + Future eventually(Supplier> mapper); /** * Apply a {@code mapper} function on this future.

@@ -521,6 +536,17 @@ default Future andThen(Handler> handler) { }); } + /** + * Returns a future succeeded or failed with the outcome of this future when it happens before the timeout fires. When + * the timeout fires before, the future is failed with a {@link java.util.concurrent.TimeoutException}, guaranteeing + * the returned future to complete within the specified {@code delay}. + * + * @param delay the delay + * @param unit the unit of the delay + * @return the timeout future + */ + Future timeout(long delay, TimeUnit unit); + /** * Bridges this Vert.x future to a {@link CompletionStage} instance. *

@@ -585,4 +611,47 @@ static Future fromCompletionStage(CompletionStage completionStage, Con }); return promise.future(); } + + /** + * Park the current thread until the {@code future} is completed, when the future + * is completed the thread is un-parked and + * + *

    + *
  • the result value is returned when the future was completed with a result
  • + *
  • otherwise, the failure is thrown
  • + *
+ * + * This method must be called from a virtual thread. + * + * @return the result + * @throws IllegalStateException when called from an event-loop thread or a non Vert.x thread + */ + default T await() { + io.vertx.core.impl.WorkerExecutor executor = io.vertx.core.impl.WorkerExecutor.unwrapWorkerExecutor(); + io.vertx.core.impl.WorkerExecutor.TaskController cont = executor.current(); + onComplete(ar -> cont.resume()); + try { + cont.suspendAndAwaitResume(); + } catch (InterruptedException e) { + Utils.throwAsUnchecked(e); + return null; + } + if (succeeded()) { + return result(); + } else { + Utils.throwAsUnchecked(cause()); + return null; + } + } + + /** + * Calls {@link #await()} on {@code future}. + * + * @param future the future to await + * @return the result + * @throws IllegalStateException when called from an event-loop thread or a non Vert.x thread + */ + static T await(Future future) { + return future.await(); + } } diff --git a/src/main/java/io/vertx/core/Launcher.java b/src/main/java/io/vertx/core/Launcher.java deleted file mode 100644 index e8b7f78c242..00000000000 --- a/src/main/java/io/vertx/core/Launcher.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core; - -import io.vertx.core.impl.launcher.VertxCommandLauncher; -import io.vertx.core.impl.launcher.VertxLifecycleHooks; -import io.vertx.core.json.JsonObject; - - -/** - * A {@code main()} class that can be used to create Vert.x instance and deploy a verticle, or run a bare Vert.x instance. - *

- * This class is used by the {@code vertx} command line utility to deploy verticles from the command line. - * It is extensible as "commands" can be added using the {@link io.vertx.core.spi.launcher.CommandFactory} - * SPI. - *

- * E.g. - *

- * {@code vertx run myverticle.js} - * {@code vertx my-command ...} - *

- * It can also be used as the main class of an executable jar so you can run verticles directly with: - *

- * {@code java -jar myapp.jar} - * - * @author Clement Escoffier - */ -public class Launcher extends VertxCommandLauncher implements VertxLifecycleHooks { - - /** - * Main entry point. - * - * @param args the user command line arguments. - */ - public static void main(String[] args) { - new Launcher().dispatch(args); - } - - /** - * Utility method to execute a specific command. - * - * @param cmd the command - * @param args the arguments - */ - public static void executeCommand(String cmd, String... args) { - new Launcher().execute(cmd, args); - } - - /** - * Hook for sub-classes of {@link Launcher} after the config has been parsed. - * - * @param config the read config, empty if none are provided. - */ - public void afterConfigParsed(JsonObject config) { - } - - /** - * Hook for sub-classes of {@link Launcher} before the vertx instance is started. - * - * @param options the configured Vert.x options. Modify them to customize the Vert.x instance. - */ - public void beforeStartingVertx(VertxOptions options) { - - } - - /** - * Hook for sub-classes of {@link Launcher} after the vertx instance is started. - * - * @param vertx the created Vert.x instance - */ - public void afterStartingVertx(Vertx vertx) { - - } - - /** - * Hook for sub-classes of {@link Launcher} before the verticle is deployed. - * - * @param deploymentOptions the current deployment options. Modify them to customize the deployment. - */ - public void beforeDeployingVerticle(DeploymentOptions deploymentOptions) { - - } - - @Override - public void beforeStoppingVertx(Vertx vertx) { - - } - - @Override - public void afterStoppingVertx() { - - } - - /** - * A deployment failure has been encountered. You can override this method to customize the behavior. - * By default it closes the `vertx` instance. - * - * @param vertx the vert.x instance - * @param mainVerticle the verticle - * @param deploymentOptions the verticle deployment options - * @param cause the cause of the failure - */ - public void handleDeployFailed(Vertx vertx, String mainVerticle, DeploymentOptions deploymentOptions, Throwable cause) { - // Default behaviour is to close Vert.x if the deploy failed - vertx.close(); - } -} diff --git a/src/main/java/docoverride/buffer/Examples.java b/src/main/java/io/vertx/core/ThreadingModel.java similarity index 54% rename from src/main/java/docoverride/buffer/Examples.java rename to src/main/java/io/vertx/core/ThreadingModel.java index 9078dd290d1..a5cc9122c57 100644 --- a/src/main/java/docoverride/buffer/Examples.java +++ b/src/main/java/io/vertx/core/ThreadingModel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -8,20 +8,29 @@ * * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ - -package docoverride.buffer; - -import io.vertx.core.buffer.Buffer; -import io.vertx.docgen.Source; +package io.vertx.core; /** + * The threading model defines how user tasks should be executed. + * * @author Julien Viet */ -@Source -public class Examples { +public enum ThreadingModel { + + /** + * Event-loop threading model. + */ + EVENT_LOOP, + + /** + * Worker threading model + */ + WORKER, + + /** + * Virtual thread threading model + */ + VIRTUAL_THREAD - public void example4() { - byte[] bytes = new byte[] {1, 3, 5}; - Buffer buff = Buffer.buffer(bytes); - } } + diff --git a/src/main/java/io/vertx/core/Timer.java b/src/main/java/io/vertx/core/Timer.java new file mode 100644 index 00000000000..2523c47d1c7 --- /dev/null +++ b/src/main/java/io/vertx/core/Timer.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core; + +import io.vertx.codegen.annotations.VertxGen; + +/** + * A timer task that can be used as a future. + * + * The future is completed when the timeout expires, when the task is cancelled the future is failed + * with a {@link java.util.concurrent.CancellationException}. + * + * @author Julien Viet + */ +@VertxGen +public interface Timer extends Future { + + /** + * Attempt to cancel the timer task, when the timer is cancelled, the timer is + * failed with a {@link java.util.concurrent.CancellationException}. + * + * @return {@code true} when the future was cancelled and the timeout didn't fire. + */ + boolean cancel(); + +} diff --git a/src/main/java/io/vertx/core/Vertx.java b/src/main/java/io/vertx/core/Vertx.java index 2ddb3826186..4037e7e962a 100644 --- a/src/main/java/io/vertx/core/Vertx.java +++ b/src/main/java/io/vertx/core/Vertx.java @@ -11,7 +11,6 @@ package io.vertx.core; -import io.netty.channel.EventLoopGroup; import io.vertx.codegen.annotations.*; import io.vertx.core.datagram.DatagramSocket; import io.vertx.core.datagram.DatagramSocketOptions; @@ -19,13 +18,10 @@ import io.vertx.core.dns.DnsClientOptions; import io.vertx.core.eventbus.EventBus; import io.vertx.core.file.FileSystem; -import io.vertx.core.http.HttpClient; -import io.vertx.core.http.HttpClientOptions; -import io.vertx.core.http.HttpServer; -import io.vertx.core.http.HttpServerOptions; +import io.vertx.core.http.*; import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.VertxBuilder; -import io.vertx.core.impl.resolver.DnsResolverProvider; +import io.vertx.core.dns.impl.DnsAddressResolverProvider; import io.vertx.core.metrics.Measured; import io.vertx.core.net.NetClient; import io.vertx.core.net.NetClientOptions; @@ -33,8 +29,12 @@ import io.vertx.core.net.NetServerOptions; import io.vertx.core.shareddata.SharedData; import io.vertx.core.spi.VerticleFactory; +import io.vertx.core.spi.VertxMetricsFactory; +import io.vertx.core.spi.VertxTracerFactory; +import io.vertx.core.spi.cluster.ClusterManager; import java.util.Set; +import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -67,6 +67,57 @@ @VertxGen public interface Vertx extends Measured { + /** + * Return a builder for Vert.x instances which allows to specify SPI such as cluster manager, metrics or tracing. + * + * @return a Vert.x instance builder + */ + static io.vertx.core.VertxBuilder builder() { + return new io.vertx.core.VertxBuilder() { + private VertxOptions options; + private ClusterManager clusterManager; + private VertxMetricsFactory metricsFactory; + private VertxTracerFactory tracerFactory; + @Override + public io.vertx.core.VertxBuilder with(VertxOptions options) { + this.options = options; + return this; + } + @Override + public io.vertx.core.VertxBuilder withMetrics(VertxMetricsFactory factory) { + this.metricsFactory = factory; + return this; + } + @Override + public io.vertx.core.VertxBuilder withTracer(VertxTracerFactory factory) { + this.tracerFactory = factory; + return this; + } + @Override + public io.vertx.core.VertxBuilder withClusterManager(ClusterManager clusterManager) { + this.clusterManager = clusterManager; + return this; + } + @Override + public Vertx build() { + VertxBuilder builder = new VertxBuilder(options != null ? options : new VertxOptions()); + builder.metricsFactory(metricsFactory); + builder.tracerFactory(tracerFactory); + builder.init(); + return builder.vertx(); + } + @Override + public Future buildClustered() { + VertxBuilder builder = new VertxBuilder(options != null ? options : new VertxOptions()); + builder.clusterManager(clusterManager); + builder.metricsFactory(metricsFactory); + builder.tracerFactory(tracerFactory); + builder.init(); + return builder.clusteredVertx(); + } + }; + } + /** * Creates a non clustered instance using default options. * @@ -166,12 +217,60 @@ default HttpServer createHttpServer() { } /** - * Create a HTTP/HTTPS client using the specified options + * Create a WebSocket client using default options + * + * @return the client + */ + default WebSocketClient createWebSocketClient() { + return createWebSocketClient(new WebSocketClientOptions()); + } + + /** + * Create a WebSocket client using the specified options * * @param options the options to use * @return the client */ - HttpClient createHttpClient(HttpClientOptions options); + WebSocketClient createWebSocketClient(WebSocketClientOptions options); + + /** + * Provide a builder for {@link HttpClient}, it can be used to configure advanced + * HTTP client settings like a redirect handler or a connection handler. + *

+ * Example usage: {@code HttpClient client = vertx.httpClientBuilder().with(options).withConnectHandler(conn -> ...).build()} + */ + HttpClientBuilder httpClientBuilder(); + + /** + * Create a HTTP/HTTPS client using the specified client and pool options + * + * @param clientOptions the client options to use + * @param poolOptions the pool options to use + * @return the client + */ + default HttpClient createHttpClient(HttpClientOptions clientOptions, PoolOptions poolOptions) { + return httpClientBuilder().with(clientOptions).with(poolOptions).build(); + } + + /** + * Create a HTTP/HTTPS client using the specified client options + * + * @param clientOptions the options to use + * @return the client + */ + default HttpClient createHttpClient(HttpClientOptions clientOptions) { + return createHttpClient(clientOptions, new PoolOptions()); + } + + /** + * Create a HTTP/HTTPS client using the specified pool options + * + * @param poolOptions the pool options to use + * @return the client + */ + default HttpClient createHttpClient(PoolOptions poolOptions) { + return createHttpClient(new HttpClientOptions(), poolOptions); + } /** * Create a HTTP/HTTPS client using default options @@ -179,9 +278,8 @@ default HttpServer createHttpServer() { * @return the client */ default HttpClient createHttpClient() { - return createHttpClient(new HttpClientOptions()); + return createHttpClient(new HttpClientOptions(), new PoolOptions()); } - /** * Create a datagram socket using the specified options * @@ -228,7 +326,7 @@ default DatagramSocket createDatagramSocket() { /** * Create a DNS client to connect to the DNS server configured by {@link VertxOptions#getAddressResolverOptions()} *

- * DNS client takes the first configured resolver address provided by {@link DnsResolverProvider#nameServerAddresses()}} + * DNS client takes the first configured resolver address provided by {@link DnsAddressResolverProvider#nameServerAddresses()}} * * @return the DNS client */ @@ -250,6 +348,24 @@ default DatagramSocket createDatagramSocket() { @CacheReturn SharedData sharedData(); + /** + * Like {@link #timer(long, TimeUnit)} with a unit in millis. + */ + default Timer timer(long delay) { + return timer(delay, TimeUnit.MILLISECONDS); + } + + /** + * Create a timer task configured with the specified {@code delay}, when the timeout fires the timer future + * is succeeded, when the timeout is cancelled the timer future is failed with a {@link java.util.concurrent.CancellationException} + * instance. + * + * @param delay the delay + * @param unit the delay unit + * @return the timer object + */ + Timer timer(long delay, TimeUnit unit); + /** * Set a one-shot timer to fire after {@code delay} milliseconds, at which point {@code handler} will be called with * the id of the timer. @@ -445,15 +561,11 @@ default Future deployVerticle(String name) { *

* Executes the blocking code in the handler {@code blockingCodeHandler} using a thread from the worker pool. *

- * When the code is complete the handler {@code resultHandler} will be called with the result on the original context - * (e.g. on the original event loop of the caller). - *

- * A {@code Future} instance is passed into {@code blockingCodeHandler}. When the blocking code successfully completes, - * the handler should call the {@link Promise#complete} or {@link Promise#complete(Object)} method, or the {@link Promise#fail} - * method if it failed. + * The returned future will be completed with the result on the original context (i.e. on the original event loop of the caller) + * or failed when the handler throws an exception. *

* In the {@code blockingCodeHandler} the current context remains the original context and therefore any task - * scheduled in the {@code blockingCodeHandler} will be executed on the this context and not on the worker thread. + * scheduled in the {@code blockingCodeHandler} will be executed on this context and not on the worker thread. *

* The blocking code should block for a reasonable amount of time (i.e no more than a few seconds). Long blocking operations * or polling operations (i.e a thread that spin in a loop polling events in a blocking fashion) are precluded. @@ -471,26 +583,20 @@ default Future deployVerticle(String name) { * @param the type of the result * @return a future completed when the blocking code is complete */ - default Future<@Nullable T> executeBlocking(Handler> blockingCodeHandler, boolean ordered) { + @GenIgnore(GenIgnore.PERMITTED_TYPE) + default Future<@Nullable T> executeBlocking(Callable blockingCodeHandler, boolean ordered) { Context context = getOrCreateContext(); return context.executeBlocking(blockingCodeHandler, ordered); } /** - * Like {@link #executeBlocking(Handler, boolean)} called with ordered = true. + * Like {@link #executeBlocking(Callable, boolean)} called with ordered = true. */ - default Future executeBlocking(Handler> blockingCodeHandler) { + @GenIgnore(GenIgnore.PERMITTED_TYPE) + default Future<@Nullable T> executeBlocking(Callable blockingCodeHandler) { return executeBlocking(blockingCodeHandler, true); } - /** - * Return the Netty EventLoopGroup used by Vert.x - * - * @return the EventLoopGroup - */ - @GenIgnore(GenIgnore.PERMITTED_TYPE) - EventLoopGroup nettyEventLoopGroup(); - /** * Like {@link #createSharedWorkerExecutor(String, int)} but with the {@link VertxOptions#setWorkerPoolSize} {@code poolSize}. */ diff --git a/src/main/java/io/vertx/core/VertxBuilder.java b/src/main/java/io/vertx/core/VertxBuilder.java new file mode 100644 index 00000000000..87a27322824 --- /dev/null +++ b/src/main/java/io/vertx/core/VertxBuilder.java @@ -0,0 +1,98 @@ +package io.vertx.core; + +import io.vertx.codegen.annotations.Fluent; +import io.vertx.codegen.annotations.GenIgnore; +import io.vertx.codegen.annotations.VertxGen; +import io.vertx.core.metrics.MetricsOptions; +import io.vertx.core.spi.VertxMetricsFactory; +import io.vertx.core.spi.VertxTracerFactory; +import io.vertx.core.spi.cluster.ClusterManager; + +/** + * A builder for creating Vert.x instances, allowing to configure Vert.x plugins: + * + *

    + *
  • metrics
  • + *
  • tracing
  • + *
  • cluster manager
  • + *
+ * + * Example usage: + * + *

+ *   Vertx vertx = Vertx.builder().with(options).withMetrics(metricsFactory).build();
+ * 
+ * + * @author Julien Viet + */ +@VertxGen +public interface VertxBuilder { + + /** + * Configure the Vert.x options. + * @param options the Vert.x options + * @return a reference to this, so the API can be used fluently + */ + @Fluent + VertxBuilder with(VertxOptions options); + + /** + * Programmatically set the metrics factory to be used when metrics are enabled. + *

+ * Only valid if {@link MetricsOptions#isEnabled} = true. + *

+ * Normally Vert.x will look on the classpath for a metrics factory implementation, but if you want to set one + * programmatically you can use this method. + * + * @param factory the metrics factory + * @return a reference to this, so the API can be used fluently + */ + @GenIgnore(GenIgnore.PERMITTED_TYPE) + @Fluent + VertxBuilder withMetrics(VertxMetricsFactory factory); + + /** + * Programmatically set the tracer factory to be used when tracing are enabled. + *

+ * Normally Vert.x will look on the classpath for a tracer factory implementation, but if you want to set one + * programmatically you can use this method. + * + * @param factory the tracer factory + * @return a reference to this, so the API can be used fluently + */ + @GenIgnore(GenIgnore.PERMITTED_TYPE) + @Fluent + VertxBuilder withTracer(VertxTracerFactory factory); + + /** + * Programmatically set the cluster manager to be used when clustering. + *

+ * Only valid if clustered = true. + *

+ * Normally Vert.x will look on the classpath for a cluster manager, but if you want to set one + * programmatically you can use this method. + * + * @param clusterManager the cluster manager + * @return a reference to this, so the API can be used fluently + */ + @GenIgnore(GenIgnore.PERMITTED_TYPE) + @Fluent + VertxBuilder withClusterManager(ClusterManager clusterManager); + + /** + * Creates a non clustered instance. + * + * @return the instance + */ + Vertx build(); + + /** + * Creates a clustered instance. + *

+ * The instance is created asynchronously and the returned future is completed with the result when it is ready. + * + * @return a future completed with the clustered vertx + */ + Future buildClustered(); + +} diff --git a/src/main/java/io/vertx/core/VertxOptions.java b/src/main/java/io/vertx/core/VertxOptions.java index 53870a843aa..4db4ff845f2 100644 --- a/src/main/java/io/vertx/core/VertxOptions.java +++ b/src/main/java/io/vertx/core/VertxOptions.java @@ -12,13 +12,13 @@ package io.vertx.core; import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.dns.AddressResolverOptions; import io.vertx.core.eventbus.EventBusOptions; import io.vertx.core.file.FileSystemOptions; import io.vertx.core.impl.cpu.CpuCoreSensor; import io.vertx.core.json.JsonObject; import io.vertx.core.metrics.MetricsOptions; -import io.vertx.core.spi.cluster.ClusterManager; import io.vertx.core.tracing.TracingOptions; import java.util.Objects; @@ -29,7 +29,8 @@ * * @author Tim Fox */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public class VertxOptions { private static final String DISABLE_TCCL_PROP_NAME = "vertx.disableTCCL"; @@ -126,7 +127,6 @@ public class VertxOptions { private long blockedThreadCheckInterval = DEFAULT_BLOCKED_THREAD_CHECK_INTERVAL; private long maxEventLoopExecuteTime = DEFAULT_MAX_EVENT_LOOP_EXECUTE_TIME; private long maxWorkerExecuteTime = DEFAULT_MAX_WORKER_EXECUTE_TIME; - private ClusterManager clusterManager; private boolean haEnabled = DEFAULT_HA_ENABLED; private int quorumSize = DEFAULT_QUORUM_SIZE; private String haGroup = DEFAULT_HA_GROUP; @@ -162,7 +162,6 @@ public VertxOptions(VertxOptions other) { this.maxEventLoopExecuteTime = other.getMaxEventLoopExecuteTime(); this.maxWorkerExecuteTime = other.getMaxWorkerExecuteTime(); this.internalBlockingPoolSize = other.getInternalBlockingPoolSize(); - this.clusterManager = other.getClusterManager(); this.haEnabled = other.isHAEnabled(); this.quorumSize = other.getQuorumSize(); this.haGroup = other.getHAGroup(); @@ -171,6 +170,7 @@ public VertxOptions(VertxOptions other) { this.warningExceptionTime = other.warningExceptionTime; this.eventBusOptions = new EventBusOptions(other.eventBusOptions); this.addressResolverOptions = other.addressResolverOptions != null ? new AddressResolverOptions(other.getAddressResolverOptions()) : null; + this.preferNativeTransport = other.preferNativeTransport; this.maxEventLoopExecuteTimeUnit = other.maxEventLoopExecuteTimeUnit; this.maxWorkerExecuteTimeUnit = other.maxWorkerExecuteTimeUnit; this.warningExceptionTimeUnit = other.warningExceptionTimeUnit; @@ -331,35 +331,6 @@ public VertxOptions setMaxWorkerExecuteTime(long maxWorkerExecuteTime) { return this; } - /** - * Get the cluster manager to be used when clustering. - *

- * If the cluster manager has been programmatically set here, then that will be used when clustering. - *

- * Otherwise Vert.x attempts to locate a cluster manager on the classpath. - * - * @return the cluster manager. - */ - public ClusterManager getClusterManager() { - return clusterManager; - } - - /** - * Programmatically set the cluster manager to be used when clustering. - *

- * Only valid if clustered = true. - *

- * Normally Vert.x will look on the classpath for a cluster manager, but if you want to set one - * programmatically you can use this method. - * - * @param clusterManager the cluster manager - * @return a reference to this, so the API can be used fluently - */ - public VertxOptions setClusterManager(ClusterManager clusterManager) { - this.clusterManager = clusterManager; - return this; - } - /** * Get the value of internal blocking pool size. *

@@ -725,7 +696,6 @@ public String toString() { ", maxEventLoopExecuteTime=" + maxEventLoopExecuteTime + ", maxWorkerExecuteTimeUnit=" + maxWorkerExecuteTimeUnit + ", maxWorkerExecuteTime=" + maxWorkerExecuteTime + - ", clusterManager=" + clusterManager + ", haEnabled=" + haEnabled + ", preferNativeTransport=" + preferNativeTransport + ", quorumSize=" + quorumSize + diff --git a/src/main/java/io/vertx/core/WorkerExecutor.java b/src/main/java/io/vertx/core/WorkerExecutor.java index 804d19fac7f..6b50d1a68fe 100644 --- a/src/main/java/io/vertx/core/WorkerExecutor.java +++ b/src/main/java/io/vertx/core/WorkerExecutor.java @@ -11,10 +11,13 @@ package io.vertx.core; +import io.vertx.codegen.annotations.GenIgnore; import io.vertx.codegen.annotations.Nullable; import io.vertx.codegen.annotations.VertxGen; import io.vertx.core.metrics.Measured; +import java.util.concurrent.Callable; + /** * An executor for executing blocking code in Vert.x .

* @@ -31,15 +34,11 @@ public interface WorkerExecutor extends Measured { *

* Executes the blocking code in the handler {@code blockingCodeHandler} using a thread from the worker pool. *

- * When the code is complete the handler {@code resultHandler} will be called with the result on the original context - * (i.e. on the original event loop of the caller). - *

- * A {@code Future} instance is passed into {@code blockingCodeHandler}. When the blocking code successfully completes, - * the handler should call the {@link Promise#complete} or {@link Promise#complete(Object)} method, or the {@link Promise#fail} - * method if it failed. + * The returned future will be completed with the result on the original context (i.e. on the original event loop of the caller) + * or failed when the handler throws an exception. *

* In the {@code blockingCodeHandler} the current context remains the original context and therefore any task - * scheduled in the {@code blockingCodeHandler} will be executed on the this context and not on the worker thread. + * scheduled in the {@code blockingCodeHandler} will be executed on this context and not on the worker thread. * * @param blockingCodeHandler handler representing the blocking code to run * @param ordered if true then if executeBlocking is called several times on the same context, the executions @@ -48,12 +47,14 @@ public interface WorkerExecutor extends Measured { * @param the type of the result * @return a future notified with the result */ - Future<@Nullable T> executeBlocking(Handler> blockingCodeHandler, boolean ordered); + @GenIgnore(GenIgnore.PERMITTED_TYPE) + Future<@Nullable T> executeBlocking(Callable blockingCodeHandler, boolean ordered); /** - * Like {@link #executeBlocking(Handler, boolean)} called with ordered = true. + * Like {@link #executeBlocking(Callable, boolean)} called with ordered = true. */ - default Future executeBlocking(Handler> blockingCodeHandler) { + @GenIgnore(GenIgnore.PERMITTED_TYPE) + default Future executeBlocking(Callable blockingCodeHandler) { return executeBlocking(blockingCodeHandler, true); } diff --git a/src/main/java/io/vertx/core/buffer/Buffer.java b/src/main/java/io/vertx/core/buffer/Buffer.java index 1b919d96809..46b4ca7f887 100644 --- a/src/main/java/io/vertx/core/buffer/Buffer.java +++ b/src/main/java/io/vertx/core/buffer/Buffer.java @@ -12,11 +12,11 @@ package io.vertx.core.buffer; -import io.netty.buffer.ByteBuf; +import io.vertx.codegen.annotations.DataObject; import io.vertx.codegen.annotations.Fluent; import io.vertx.codegen.annotations.GenIgnore; import io.vertx.codegen.annotations.VertxGen; -import io.vertx.core.buffer.impl.BufferImpl; +import io.vertx.core.buffer.impl.BufferInternal; import io.vertx.core.json.Json; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; @@ -25,7 +25,6 @@ import java.nio.ByteBuffer; import java.nio.charset.Charset; -import java.util.Objects; /** * Most data is shuffled around inside Vert.x using buffers. @@ -37,7 +36,7 @@ * * @author Tim Fox */ -@VertxGen +@DataObject public interface Buffer extends ClusterSerializable, Shareable { /** @@ -46,7 +45,7 @@ public interface Buffer extends ClusterSerializable, Shareable { * @return the buffer */ static Buffer buffer() { - return BufferImpl.buffer(); + return BufferInternal.buffer(); } /** @@ -59,7 +58,7 @@ static Buffer buffer() { * @return the buffer */ static Buffer buffer(int initialSizeHint) { - return BufferImpl.buffer(initialSizeHint); + return BufferInternal.buffer(initialSizeHint); } /** @@ -69,7 +68,7 @@ static Buffer buffer(int initialSizeHint) { * @return the buffer */ static Buffer buffer(String string) { - return BufferImpl.buffer(string); + return BufferInternal.buffer(string); } /** @@ -80,7 +79,7 @@ static Buffer buffer(String string) { * @return the buffer */ static Buffer buffer(String string, String enc) { - return BufferImpl.buffer(string, enc); + return BufferInternal.buffer(string, enc); } /** @@ -91,30 +90,7 @@ static Buffer buffer(String string, String enc) { */ @GenIgnore(GenIgnore.PERMITTED_TYPE) static Buffer buffer(byte[] bytes) { - return BufferImpl.buffer(bytes); - } - - /** - *

- * Create a new buffer from a Netty {@code ByteBuf}. - * Note that the returned buffer is backed by given Netty ByteBuf, - * so changes in the returned buffer are reflected in given Netty ByteBuf, and vice-versa. - *

- *

- * For example, both buffers in the code below share their data: - *

- *
-   *   Buffer src = Buffer.buffer();
-   *   Buffer clone = Buffer.buffer(src.getByteBuf());
-   * 
- * - * @param byteBuf the Netty ByteBuf - * @return the buffer - */ - @GenIgnore(GenIgnore.PERMITTED_TYPE) - static Buffer buffer(ByteBuf byteBuf) { - Objects.requireNonNull(byteBuf); - return BufferImpl.buffer(byteBuf); + return BufferInternal.buffer(bytes); } /** @@ -148,6 +124,7 @@ static Buffer buffer(ByteBuf byteBuf) { * * @return a JSON element which can be a {@link JsonArray}, {@link JsonObject}, {@link String}, ...etc if the buffer contains an array, object, string, ...etc */ + @GenIgnore default Object toJson() { return Json.CODEC.fromBuffer(this, Object.class); } @@ -215,6 +192,13 @@ default Object toJson() { */ double getDouble(int pos); + /** + * Gets a double at the specified absolute {@code index} in this buffer in Little Endian Byte Order. + * + * @throws IndexOutOfBoundsException if the specified {@code pos} is less than {@code 0} or {@code pos + 8} is greater than the length of the Buffer. + */ + double getDoubleLE(int pos); + /** * Returns the {@code float} at position {@code pos} in the Buffer. * @@ -222,6 +206,13 @@ default Object toJson() { */ float getFloat(int pos); + /** + * Gets a float at the specified absolute {@code index} in this buffer in Little Endian Byte Order. + * + * @throws IndexOutOfBoundsException if the specified {@code pos} is less than {@code 0} or {@code pos + 4} is greater than the length of the Buffer. + */ + float getFloatLE(int pos); + /** * Returns the {@code short} at position {@code pos} in the Buffer. * @@ -489,6 +480,13 @@ default Object toJson() { @Fluent Buffer appendFloat(float f); + /** + * Appends the specified unsigned {@code float} to the end of the Buffer in the Little Endian Byte Order.The buffer will expand as necessary to accommodate any bytes written.

+ * Returns a reference to {@code this} so multiple operations can be appended together. + */ + @Fluent + Buffer appendFloatLE(float f); + /** * Appends the specified {@code double} to the end of the Buffer. The buffer will expand as necessary to accommodate any bytes written.

* Returns a reference to {@code this} so multiple operations can be appended together. @@ -496,6 +494,13 @@ default Object toJson() { @Fluent Buffer appendDouble(double d); + /** + * Appends the specified unsigned {@code double} to the end of the Buffer in the Little Endian Byte Order.The buffer will expand as necessary to accommodate any bytes written.

+ * Returns a reference to {@code this} so multiple operations can be appended together. + */ + @Fluent + Buffer appendDoubleLE(double d); + /** * Appends the specified {@code String} to the end of the Buffer with the encoding as specified by {@code enc}.

* The buffer will expand as necessary to accommodate any bytes written.

@@ -589,6 +594,13 @@ default Object toJson() { @Fluent Buffer setDouble(int pos, double d); + /** + * Sets the {@code double} at position {@code pos} in the Buffer to the value {@code d} in the Little Endian Byte Order.

+ * The buffer will expand as necessary to accommodate any value written. + */ + @Fluent + Buffer setDoubleLE(int pos, double d); + /** * Sets the {@code float} at position {@code pos} in the Buffer to the value {@code f}.

* The buffer will expand as necessary to accommodate any value written. @@ -596,6 +608,13 @@ default Object toJson() { @Fluent Buffer setFloat(int pos, float f); + /** + * Sets the {@code float} at position {@code pos} in the Buffer to the value {@code f} in the Little Endian Byte Order.

+ * The buffer will expand as necessary to accommodate any value written. + */ + @Fluent + Buffer setFloatLE(int pos, float f); + /** * Sets the {@code short} at position {@code pos} in the Buffer to the value {@code s}.

* The buffer will expand as necessary to accommodate any value written. @@ -701,12 +720,4 @@ default Object toJson() { */ Buffer slice(int start, int end); - /** - * Returns the Buffer as a Netty {@code ByteBuf}. - * - *

The returned buffer is a duplicate that maintain its own indices. - */ - @GenIgnore(GenIgnore.PERMITTED_TYPE) - ByteBuf getByteBuf(); - } diff --git a/src/main/java/io/vertx/core/buffer/impl/BufferImpl.java b/src/main/java/io/vertx/core/buffer/impl/BufferImpl.java index 38af5a8f0cd..0f49291b5bf 100644 --- a/src/main/java/io/vertx/core/buffer/impl/BufferImpl.java +++ b/src/main/java/io/vertx/core/buffer/impl/BufferImpl.java @@ -36,31 +36,7 @@ * * @author Tim Fox */ -public class BufferImpl implements Buffer { - - public static Buffer buffer(int initialSizeHint) { - return new BufferImpl(initialSizeHint); - } - - public static Buffer buffer() { - return new BufferImpl(); - } - - public static Buffer buffer(String str) { - return new BufferImpl(str); - } - - public static Buffer buffer(String str, String enc) { - return new BufferImpl(str, enc); - } - - public static Buffer buffer(byte[] bytes) { - return new BufferImpl(bytes); - } - - public static Buffer buffer(ByteBuf byteBuffer) { - return new BufferImpl(byteBuffer); - } +public class BufferImpl implements BufferInternal { private ByteBuf buffer; @@ -160,11 +136,21 @@ public double getDouble(int pos) { return buffer.getDouble(pos); } + public double getDoubleLE(int pos) { + checkUpperBound(pos, 8); + return buffer.getDoubleLE(pos); + } + public float getFloat(int pos) { checkUpperBound(pos, 4); return buffer.getFloat(pos); } + public float getFloatLE(int pos) { + checkUpperBound(pos, 4); + return buffer.getFloatLE(pos); + } + public short getShort(int pos) { checkUpperBound(pos, 2); return buffer.getShort(pos); @@ -262,14 +248,14 @@ public String getString(int start, int end) { return new String(bytes, StandardCharsets.UTF_8); } - public Buffer appendBuffer(Buffer buff) { + public BufferImpl appendBuffer(Buffer buff) { BufferImpl impl = (BufferImpl) buff; ByteBuf byteBuf = impl.buffer; buffer.writeBytes(impl.buffer, byteBuf.readerIndex(), impl.buffer.readableBytes()); return this; } - public Buffer appendBuffer(Buffer buff, int offset, int len) { + public BufferImpl appendBuffer(Buffer buff, int offset, int len) { BufferImpl impl = (BufferImpl) buff; ByteBuf byteBuf = impl.buffer; int from = byteBuf.readerIndex() + offset; @@ -277,201 +263,227 @@ public Buffer appendBuffer(Buffer buff, int offset, int len) { return this; } - public Buffer appendBytes(byte[] bytes) { + public BufferImpl appendBytes(byte[] bytes) { buffer.writeBytes(bytes); return this; } - public Buffer appendBytes(byte[] bytes, int offset, int len) { + public BufferImpl appendBytes(byte[] bytes, int offset, int len) { buffer.writeBytes(bytes, offset, len); return this; } - public Buffer appendByte(byte b) { + public BufferImpl appendByte(byte b) { buffer.writeByte(b); return this; } - public Buffer appendUnsignedByte(short b) { + public BufferImpl appendUnsignedByte(short b) { buffer.writeByte(b); return this; } - public Buffer appendInt(int i) { + public BufferImpl appendInt(int i) { buffer.writeInt(i); return this; } - public Buffer appendIntLE(int i) { + public BufferImpl appendIntLE(int i) { buffer.writeIntLE(i); return this; } - public Buffer appendUnsignedInt(long i) { + public BufferImpl appendUnsignedInt(long i) { buffer.writeInt((int) i); return this; } - public Buffer appendUnsignedIntLE(long i) { + public BufferImpl appendUnsignedIntLE(long i) { buffer.writeIntLE((int) i); return this; } - public Buffer appendMedium(int i) { + public BufferImpl appendMedium(int i) { buffer.writeMedium(i); return this; } - public Buffer appendMediumLE(int i) { + public BufferImpl appendMediumLE(int i) { buffer.writeMediumLE(i); return this; } - public Buffer appendLong(long l) { + public BufferImpl appendLong(long l) { buffer.writeLong(l); return this; } - public Buffer appendLongLE(long l) { + public BufferImpl appendLongLE(long l) { buffer.writeLongLE(l); return this; } - public Buffer appendShort(short s) { + public BufferImpl appendShort(short s) { buffer.writeShort(s); return this; } - public Buffer appendShortLE(short s) { + public BufferImpl appendShortLE(short s) { buffer.writeShortLE(s); return this; } - public Buffer appendUnsignedShort(int s) { + public BufferImpl appendUnsignedShort(int s) { buffer.writeShort(s); return this; } - public Buffer appendUnsignedShortLE(int s) { + public BufferImpl appendUnsignedShortLE(int s) { buffer.writeShortLE(s); return this; } - public Buffer appendFloat(float f) { + public BufferImpl appendFloat(float f) { buffer.writeFloat(f); return this; } - public Buffer appendDouble(double d) { + @Override + public BufferImpl appendFloatLE(float f) { + buffer.writeFloatLE(f); + return this; + } + + public BufferImpl appendDouble(double d) { buffer.writeDouble(d); return this; } - public Buffer appendString(String str, String enc) { + @Override + public BufferImpl appendDoubleLE(double d) { + buffer.writeDoubleLE(d); + return this; + } + + public BufferImpl appendString(String str, String enc) { return append(str, Charset.forName(Objects.requireNonNull(enc))); } - public Buffer appendString(String str) { + public BufferImpl appendString(String str) { return append(str, CharsetUtil.UTF_8); } - public Buffer setByte(int pos, byte b) { + public BufferImpl setByte(int pos, byte b) { ensureLength(pos + 1); buffer.setByte(pos, b); return this; } - public Buffer setUnsignedByte(int pos, short b) { + public BufferImpl setUnsignedByte(int pos, short b) { ensureLength(pos + 1); buffer.setByte(pos, b); return this; } - public Buffer setInt(int pos, int i) { + public BufferImpl setInt(int pos, int i) { ensureLength(pos + 4); buffer.setInt(pos, i); return this; } - public Buffer setIntLE(int pos, int i) { + public BufferImpl setIntLE(int pos, int i) { ensureLength(pos + 4); buffer.setIntLE(pos, i); return this; } - public Buffer setUnsignedInt(int pos, long i) { + public BufferImpl setUnsignedInt(int pos, long i) { ensureLength(pos + 4); buffer.setInt(pos, (int) i); return this; } - public Buffer setUnsignedIntLE(int pos, long i) { + public BufferImpl setUnsignedIntLE(int pos, long i) { ensureLength(pos + 4); buffer.setIntLE(pos, (int) i); return this; } - public Buffer setMedium(int pos, int i) { + public BufferImpl setMedium(int pos, int i) { ensureLength(pos + 3); buffer.setMedium(pos, i); return this; } - public Buffer setMediumLE(int pos, int i) { + public BufferImpl setMediumLE(int pos, int i) { ensureLength(pos + 3); buffer.setMediumLE(pos, i); return this; } - public Buffer setLong(int pos, long l) { + public BufferImpl setLong(int pos, long l) { ensureLength(pos + 8); buffer.setLong(pos, l); return this; } - public Buffer setLongLE(int pos, long l) { + public BufferImpl setLongLE(int pos, long l) { ensureLength(pos + 8); buffer.setLongLE(pos, l); return this; } - public Buffer setDouble(int pos, double d) { + public BufferImpl setDouble(int pos, double d) { ensureLength(pos + 8); buffer.setDouble(pos, d); return this; } - public Buffer setFloat(int pos, float f) { + @Override + public BufferImpl setDoubleLE(int pos, double d) { + ensureLength(pos + 8); + buffer.setDoubleLE(pos, d); + return this; + } + + public BufferImpl setFloat(int pos, float f) { ensureLength(pos + 4); buffer.setFloat(pos, f); return this; } - public Buffer setShort(int pos, short s) { + @Override + public BufferImpl setFloatLE(int pos, float f) { + ensureLength(pos + 4); + buffer.setFloatLE(pos, f); + return this; + } + + public BufferImpl setShort(int pos, short s) { ensureLength(pos + 2); buffer.setShort(pos, s); return this; } - public Buffer setShortLE(int pos, short s) { + public BufferImpl setShortLE(int pos, short s) { ensureLength(pos + 2); buffer.setShortLE(pos, s); return this; } - public Buffer setUnsignedShort(int pos, int s) { + public BufferImpl setUnsignedShort(int pos, int s) { ensureLength(pos + 2); buffer.setShort(pos, s); return this; } - public Buffer setUnsignedShortLE(int pos, int s) { + public BufferImpl setUnsignedShortLE(int pos, int s) { ensureLength(pos + 2); buffer.setShortLE(pos, s); return this; } - public Buffer setBuffer(int pos, Buffer buff) { + public BufferImpl setBuffer(int pos, Buffer buff) { ensureLength(pos + buff.length()); BufferImpl impl = (BufferImpl) buff; ByteBuf byteBuf = impl.buffer; @@ -479,7 +491,7 @@ public Buffer setBuffer(int pos, Buffer buff) { return this; } - public Buffer setBuffer(int pos, Buffer buffer, int offset, int len) { + public BufferImpl setBuffer(int pos, Buffer buffer, int offset, int len) { ensureLength(pos + len); BufferImpl impl = (BufferImpl) buffer; ByteBuf byteBuf = impl.buffer; @@ -493,23 +505,23 @@ public BufferImpl setBytes(int pos, ByteBuffer b) { return this; } - public Buffer setBytes(int pos, byte[] b) { + public BufferImpl setBytes(int pos, byte[] b) { ensureLength(pos + b.length); buffer.setBytes(pos, b); return this; } - public Buffer setBytes(int pos, byte[] b, int offset, int len) { + public BufferImpl setBytes(int pos, byte[] b, int offset, int len) { ensureLength(pos + len); buffer.setBytes(pos, b, offset, len); return this; } - public Buffer setString(int pos, String str) { + public BufferImpl setString(int pos, String str) { return setBytes(pos, str, CharsetUtil.UTF_8); } - public Buffer setString(int pos, String str, String enc) { + public BufferImpl setString(int pos, String str, String enc) { return setBytes(pos, str, Charset.forName(enc)); } @@ -517,15 +529,15 @@ public int length() { return buffer.writerIndex(); } - public Buffer copy() { + public BufferImpl copy() { return buffer.isReadOnly() ? this : new BufferImpl(buffer.copy()); } - public Buffer slice() { + public BufferImpl slice() { return new BufferImpl(buffer.slice()); } - public Buffer slice(int start, int end) { + public BufferImpl slice(int start, int end) { return new BufferImpl(buffer.slice(start, end - start)); } @@ -544,14 +556,14 @@ public ByteBuf getByteBuf() { return duplicate; } - private Buffer append(String str, Charset charset) { + private BufferImpl append(String str, Charset charset) { byte[] bytes = str.getBytes(charset); ensureExpandableBy(bytes.length); buffer.writeBytes(bytes); return this; } - private Buffer setBytes(int pos, String str, Charset charset) { + private BufferImpl setBytes(int pos, String str, Charset charset) { byte[] bytes = str.getBytes(charset); ensureLength(pos + bytes.length); buffer.setBytes(pos, bytes); diff --git a/src/main/java/io/vertx/core/buffer/impl/BufferInternal.java b/src/main/java/io/vertx/core/buffer/impl/BufferInternal.java new file mode 100644 index 00000000000..fc16acb7ef6 --- /dev/null +++ b/src/main/java/io/vertx/core/buffer/impl/BufferInternal.java @@ -0,0 +1,215 @@ +package io.vertx.core.buffer.impl; + +import io.netty.buffer.ByteBuf; +import io.vertx.core.buffer.Buffer; + +import java.nio.ByteBuffer; +import java.util.Objects; + +public interface BufferInternal extends Buffer { + + /** + *

+ * Create a new buffer from a Netty {@code ByteBuf}. + * Note that the returned buffer is backed by given Netty ByteBuf, + * so changes in the returned buffer are reflected in given Netty ByteBuf, and vice-versa. + *

+ *

+ * For example, both buffers in the code below share their data: + *

+ *
+   *   Buffer src = Buffer.buffer();
+   *   Buffer clone = Buffer.buffer(src.getByteBuf());
+   * 
+ * + * @param byteBuf the Netty ByteBuf + * @return the buffer + */ + static BufferInternal buffer(ByteBuf byteBuf) { + Objects.requireNonNull(byteBuf); + return new BufferImpl(byteBuf); + } + + static BufferInternal buffer(int initialSizeHint) { + return new BufferImpl(initialSizeHint); + } + + static BufferInternal buffer() { + return new BufferImpl(); + } + + static BufferInternal buffer(String str) { + return new BufferImpl(str); + } + + static BufferInternal buffer(String str, String enc) { + return new BufferImpl(str, enc); + } + + static BufferInternal buffer(byte[] bytes) { + return new BufferImpl(bytes); + } + + @Override + BufferInternal appendBuffer(Buffer buff); + + @Override + BufferInternal appendBuffer(Buffer buff, int offset, int len); + + @Override + BufferInternal appendBytes(byte[] bytes); + + @Override + BufferInternal appendBytes(byte[] bytes, int offset, int len); + + @Override + BufferInternal appendByte(byte b); + + @Override + BufferInternal appendUnsignedByte(short b); + + @Override + BufferInternal appendInt(int i); + + @Override + BufferInternal appendIntLE(int i); + + @Override + BufferInternal appendUnsignedInt(long i); + + @Override + BufferInternal appendUnsignedIntLE(long i); + + @Override + BufferInternal appendMedium(int i); + + @Override + BufferInternal appendMediumLE(int i); + + @Override + BufferInternal appendLong(long l); + + @Override + BufferInternal appendLongLE(long l); + + @Override + BufferInternal appendShort(short s); + + @Override + BufferInternal appendShortLE(short s); + + @Override + BufferInternal appendUnsignedShort(int s); + + @Override + BufferInternal appendUnsignedShortLE(int s); + + @Override + BufferInternal appendFloat(float f); + + @Override + BufferInternal appendFloatLE(float f); + + @Override + BufferInternal appendDouble(double d); + + @Override + BufferInternal appendDoubleLE(double d); + + @Override + BufferInternal appendString(String str, String enc); + + @Override + BufferInternal appendString(String str); + + @Override + BufferInternal setByte(int pos, byte b); + + @Override + BufferInternal setUnsignedByte(int pos, short b); + + @Override + BufferInternal setInt(int pos, int i); + + @Override + BufferInternal setIntLE(int pos, int i); + + @Override + BufferInternal setUnsignedInt(int pos, long i); + + @Override + BufferInternal setUnsignedIntLE(int pos, long i); + + @Override + BufferInternal setMedium(int pos, int i); + + @Override + BufferInternal setMediumLE(int pos, int i); + + @Override + BufferInternal setLong(int pos, long l); + + @Override + BufferInternal setLongLE(int pos, long l); + + @Override + BufferInternal setDouble(int pos, double d); + + @Override + BufferInternal setDoubleLE(int pos, double d); + + @Override + BufferInternal setFloat(int pos, float f); + + @Override + BufferInternal setFloatLE(int pos, float f); + + @Override + BufferInternal setShort(int pos, short s); + + @Override + BufferInternal setShortLE(int pos, short s); + + @Override + BufferInternal setUnsignedShort(int pos, int s); + + @Override + BufferInternal setUnsignedShortLE(int pos, int s); + + @Override + BufferInternal setBuffer(int pos, Buffer b); + + @Override + BufferInternal setBuffer(int pos, Buffer b, int offset, int len); + + @Override + BufferInternal setBytes(int pos, ByteBuffer b); + + @Override + BufferInternal setBytes(int pos, byte[] b); + + @Override + BufferInternal setBytes(int pos, byte[] b, int offset, int len); + + @Override + BufferInternal setString(int pos, String str); + + @Override + BufferInternal setString(int pos, String str, String enc); + + @Override + BufferInternal copy(); + + @Override + BufferInternal slice(); + + @Override + BufferInternal slice(int start, int end); + + /** + * Returns the Buffer as a Netty {@code ByteBuf}. + * + *

The returned buffer is a duplicate that maintain its own indices. + */ + ByteBuf getByteBuf(); +} diff --git a/src/main/java/io/vertx/core/buffer/impl/VertxHeapByteBuf.java b/src/main/java/io/vertx/core/buffer/impl/VertxHeapByteBuf.java index 10ca84f4ce4..35308680d8d 100644 --- a/src/main/java/io/vertx/core/buffer/impl/VertxHeapByteBuf.java +++ b/src/main/java/io/vertx/core/buffer/impl/VertxHeapByteBuf.java @@ -13,17 +13,26 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.UnpooledHeapByteBuf; -import io.netty.buffer.UnpooledUnsafeHeapByteBuf; /** * An un-releasable, un-pooled, un-instrumented heap {@code ByteBuf}. */ final class VertxHeapByteBuf extends UnpooledHeapByteBuf { + private static final byte[] EMPTY = new byte[0]; + public VertxHeapByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) { super(alloc, initialCapacity, maxCapacity); } + @Override + protected byte[] allocateArray(int initialCapacity) { + if (initialCapacity == 0) { + return EMPTY; + } + return super.allocateArray(initialCapacity); + } + @Override public ByteBuf retain(int increment) { return this; diff --git a/src/main/java/io/vertx/core/buffer/impl/VertxUnsafeHeapByteBuf.java b/src/main/java/io/vertx/core/buffer/impl/VertxUnsafeHeapByteBuf.java index 227e2c77f30..8af21866efe 100644 --- a/src/main/java/io/vertx/core/buffer/impl/VertxUnsafeHeapByteBuf.java +++ b/src/main/java/io/vertx/core/buffer/impl/VertxUnsafeHeapByteBuf.java @@ -19,10 +19,20 @@ */ final class VertxUnsafeHeapByteBuf extends UnpooledUnsafeHeapByteBuf { + private static final byte[] EMPTY = new byte[0]; + public VertxUnsafeHeapByteBuf(ByteBufAllocator alloc, int initialCapacity, int maxCapacity) { super(alloc, initialCapacity, maxCapacity); } + @Override + protected byte[] allocateArray(int initialCapacity) { + if (initialCapacity == 0) { + return EMPTY; + } + return super.allocateArray(initialCapacity); + } + @Override public ByteBuf retain(int increment) { return this; diff --git a/src/main/java/io/vertx/core/cli/AmbiguousOptionException.java b/src/main/java/io/vertx/core/cli/AmbiguousOptionException.java deleted file mode 100644 index 22b81b6531d..00000000000 --- a/src/main/java/io/vertx/core/cli/AmbiguousOptionException.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli; - - -import java.util.List; -import java.util.stream.Collectors; - -/** - * Exception thrown when the command line is ambiguous meaning it cannot determine exactly which option has to be set. - * - * @author Clement Escoffier - */ -public class AmbiguousOptionException extends CLIException { - - private final List

- * If the configuration is not valid, this method throws a {@link IllegalArgumentException}. - */ - public void ensureValidity() { - if (index < 0) { - throw new IllegalArgumentException("The index cannot be negative"); - } - } - -} diff --git a/src/main/java/io/vertx/core/cli/CLI.java b/src/main/java/io/vertx/core/cli/CLI.java deleted file mode 100644 index b52669487fb..00000000000 --- a/src/main/java/io/vertx/core/cli/CLI.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli; - -import io.vertx.codegen.annotations.Fluent; -import io.vertx.codegen.annotations.GenIgnore; -import io.vertx.codegen.annotations.Nullable; -import io.vertx.codegen.annotations.VertxGen; -import io.vertx.core.cli.annotations.CLIConfigurator; -import io.vertx.core.cli.impl.DefaultCLI; - -import java.util.List; - -/** - * Interface defining a command-line interface (in other words a command such as 'run', 'ls'...). - * This interface is polyglot to ease reuse such as in Vert.x Shell. - *

- * A command line interface has a name, and defines a set of options and arguments. Options are key-value pair such - * as {@code -foo=bar} or {@code -flag}. The supported formats depend on the used parser. Arguments are unlike - * options raw values. Options are defined using - * {@link Option}, while argument are defined using {@link Argument}. - *

- * Command line interfaces also define a summary and a description. These attributes are used in the usage generation - * . To disable the help generation, set the {@code hidden} attribute to {@code true}. - *

- * Command Line Interface object does not contains "value", it's a model. It must be evaluated by a - * parser that returns a {@link CommandLine} object containing the argument and option values. - * - * @author Clement Escoffier - * @see Argument - * @see Option - */ -@VertxGen -public interface CLI { - - /** - * Creates an instance of {@link CLI} using the default implementation. - * - * @param name the name of the CLI (must not be {@code null}) - * @return the created instance of {@link CLI} - */ - static CLI create(String name) { - return new DefaultCLI().setName(name); - } - - /** - * Creates an instance of {@link CLI} from the given Java class. It instantiates the {@link CLI} object from the - * annotations used in the class. - * - * @param clazz the annotated class - * @return the created instance of {@link CLI} - */ - @GenIgnore - static CLI create(Class clazz) { - return CLIConfigurator.define(clazz); - } - - /** - * Parses the user command line interface and create a new {@link CommandLine} containing extracting values. - * - * @param arguments the arguments - * @return the creates command line - */ - CommandLine parse(List arguments); - - /** - * Parses the user command line interface and create a new {@link CommandLine} containing extracting values. - * - * @param arguments the arguments - * @param validate enable / disable parsing validation - * @return the creates command line - */ - CommandLine parse(List arguments, boolean validate); - - /** - * @return the CLI name. - */ - String getName(); - - /** - * Sets the name of the CLI. - * - * @param name the name - * @return the current {@link CLI} instance - */ - @Fluent - CLI setName(String name); - - /** - * @return the CLI description. - */ - @Nullable String getDescription(); - - @Fluent - CLI setDescription(String desc); - - /** - * @return the CLI summary. - */ - @Nullable String getSummary(); - - /** - * Sets the summary of the CLI. - * - * @param summary the summary - * @return the current {@link CLI} instance - */ - @Fluent - CLI setSummary(String summary); - - /** - * Checks whether or not the current {@link CLI} instance is hidden. - * - * @return {@code true} if the current {@link CLI} is hidden, {@link false} otherwise - */ - boolean isHidden(); - - /** - * Sets whether or not the current instance of {@link CLI} must be hidden. Hidden CLI are not listed when - * displaying usages / help messages. In other words, hidden commands are for power user. - * - * @param hidden enables or disables the hidden aspect of the CI - * @return the current {@link CLI} instance - */ - @Fluent - CLI setHidden(boolean hidden); - - /** - * Gets the list of options. - * - * @return the list of options, empty if none. - */ - List

- * Calling this method an a non-flag option throws an {@link IllegalStateException}. - * - * @param name the option name - * @return {@code true} if the flag has been set in the command line, {@code false} otherwise. - */ - boolean isFlagEnabled(String name); - - /** - * Checks whether or not the given option has been assigned in the command line. - * - * @param option the option - * @return {@code true} if the option has received a value, {@link false} otherwise. - */ - boolean isOptionAssigned(Option option); - - /** - * Gets the raw values of the given option. Raw values are simple "String", not converted to the option type. - * - * @param option the option - * @return the list of values, empty if none - * @deprecated use {@link #getRawValuesForOption(Option)} - */ - @Deprecated - default List getRawValues(Option option) { - return getRawValuesForOption(option); - } - - /** - * Gets the raw values of the given option. Raw values are simple "String", not converted to the option type. - * - * @param option the option - * @return the list of values, empty if none - */ - List getRawValuesForOption(Option option); - - /** - * Gets the raw values of the given argument. Raw values are simple "String", not converted to the argument type. - * - * @param argument the argument - * @return the list of values, empty if none - */ - List getRawValuesForArgument(Argument argument); - - /** - * Gets the raw value of the given option. Raw values are the values as given in the user command line. - * - * @param option the option - * @return the value, {@code null} if none. - */ - @Nullable String getRawValueForOption(Option option); - - /** - * Checks whether or not the given option accept more values. - * - * @param option the option - * @return {@link true} if the option accepts more values, {@link false} otherwise. - */ - boolean acceptMoreValues(Option option); - - /** - * Gets the raw value of the given argument. Raw values are the values as given in the user command line. - * - * @param arg the argument - * @return the value, {@code null} if none. - */ - @Nullable String getRawValueForArgument(Argument arg); - - /** - * Checks whether or not the given argument has been assigned in the command line. - * - * @param arg the argument - * @return {@code true} if the argument has received a value, {@link false} otherwise. - */ - boolean isArgumentAssigned(Argument arg); - - /** - * Checks whether or not the given option has been seen in the user command line. - * - * @param option the option - * @return {@code true} if the user command line has used the option - */ - boolean isSeenInCommandLine(Option option); - - /** - * Checks whether or not the command line is valid, i.e. all constraints from arguments and options have been - * satisfied. This method is used when the parser validation is disabled. - * - * @return {@code true} if the current {@link CommandLine} object is valid. {@link false} otherwise. - */ - boolean isValid(); - - /** - * Checks whether or not the user has passed a "help" option and is asking for help. - * - * @return {@code true} if the user command line has enabled a "Help" option, {@link false} otherwise. - */ - boolean isAskingForHelp(); -} diff --git a/src/main/java/io/vertx/core/cli/InvalidValueException.java b/src/main/java/io/vertx/core/cli/InvalidValueException.java deleted file mode 100644 index 4ab7a1d4e5f..00000000000 --- a/src/main/java/io/vertx/core/cli/InvalidValueException.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli; - -/** - * Exception thrown when an option or an argument receives an invalid value. - * - * @author Clement Escoffier - */ -public class InvalidValueException extends CLIException { - private final Option option; - private final Argument argument; - private final String value; - - /** - * Creates a new instance of {@link InvalidValueException} for the given option and the given value. This - * constructor is used when the option receives a value while it does not accept another value. - * - * @param option the option - * @param value the value - */ - public InvalidValueException(Option option, String value) { - this(option, value, null); - } - - - /** - * Creates a new instance of {@link InvalidValueException} for the given argument and the given value. This - * constructor is used when the argument receives a value that cannot be "converted" to the desired type. - * - * @param argument the argument - * @param value the value - * @param cause the cause - */ - public InvalidValueException(Argument argument, String value, Exception cause) { - super("The value '" + value + "' is not accepted by the argument '" - + (argument.getArgName() != null ? argument.getArgName() : argument.getIndex()) + "'", cause); - this.option = null; - this.value = value; - this.argument = argument; - } - - /** - * Creates a new instance of {@link InvalidValueException} for the given option and the given value. This - * constructor is used when the options receives a value that cannot be "converted" to the desired type. - * - * @param option the option - * @param value the value - * @param cause the cause - */ - public InvalidValueException(Option option, String value, Exception cause) { - super("The value '" + value + "' is not accepted by '" + option.getName() + "'", cause); - this.argument = null; - this.value = value; - this.option = option; - } - - /** - * @return the option, may be {@code null} if the exception is about an argument. - */ - public Option getOption() { - return option; - } - - /** - * @return the invalid value. - */ - public String getValue() { - return value; - } - - /** - * @return the argument, may be {@code null} if the exception is about an option. - */ - public Argument getArgument() { - return argument; - } -} diff --git a/src/main/java/io/vertx/core/cli/MissingOptionException.java b/src/main/java/io/vertx/core/cli/MissingOptionException.java deleted file mode 100644 index b0329b7e299..00000000000 --- a/src/main/java/io/vertx/core/cli/MissingOptionException.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli; - -import java.util.Collection; -import java.util.stream.Collectors; - -/** - * Exception thrown when an option was expected and was not found on the command line. - * - * @author Clement Escoffier - */ -public class MissingOptionException extends CLIException { - private final Collection

- * Short name is generally used with a single dash, while long name requires a double-dash. - * - * @author Clement Escoffier - */ -@DataObject(generateConverter = true, publicConverter = false) -public class Option { - - /** - * Default name in the usage message. - */ - public static final String DEFAULT_ARG_NAME = "value"; - - /** - * The default long name / short name of the option. Notice that options requires at least a regular long name or - * short name. - */ - public static final String NO_NAME = "\0"; - - /** - * the option long name. - */ - protected String longName = NO_NAME; - - /** - * the option short name. - */ - protected String shortName = NO_NAME; - - /** - * the option name used in usage message. - */ - protected String argName = DEFAULT_ARG_NAME; - - /** - * The option description. - */ - protected String description; - - /** - * whether or not the option is required. A mandatory not set throws a {@link MissingOptionException}. - */ - protected boolean required; - - /** - * whether or not the option is hidden. Hidden options are not displayed in usage. - */ - protected boolean hidden; - - /** - * whether or not the option receives a single value. {@code true} by default. - */ - protected boolean singleValued = true; - - /** - * whether or not the option can recevie multiple values. - */ - protected boolean multiValued; - - /** - * the option default value. - */ - protected String defaultValue; - - /** - * whether or not the option is a flag. Flag option does not require a value. If an option is a flag, it is - * evaluated to {@link true} if the option is used in the command line. - */ - protected boolean flag; - - /** - * whether or not the option is a "help" option. Is the user execute the command line enabling a help option, the - * command line validation won't fail, and give the command the opportunity to display the usage message, instead - * of throwing an exception during the parsing. - */ - protected boolean help; - - /** - * if the option value has to be in a definited set, this field represents the set of values. Value are sorted - * alphabetically. - */ - protected Set choices = new TreeSet<>(); - - /** - * Creates a new empty instance of {@link Option}. - */ - public Option() { - } - - /** - * Creates a new instance of {@link Option} by copying the state of another {@link Option}. - * - * @param other the other option - */ - public Option(Option other) { - this(); - this.longName = other.longName; - this.shortName = other.shortName; - this.argName = other.argName; - this.description = other.description; - this.required = other.required; - this.hidden = other.hidden; - this.singleValued = other.singleValued; - this.multiValued = other.multiValued; - this.defaultValue = other.defaultValue; - this.flag = other.flag; - this.help = other.help; - this.choices = other.choices; - } - - /** - * Creates a new instance of {@link Option} from the given {@link JsonObject} - * - * @param json the json object representing the option - * @see #toJson() - */ - public Option(JsonObject json) { - this(); - OptionConverter.fromJson(json, this); - } - - /** - * Gets the json representation of this {@link Option}. - * - * @return the json representation - */ - public JsonObject toJson() { - JsonObject json = new JsonObject(); - OptionConverter.toJson(this, json); - return json; - } - - /** - * Checks whether or not the option is valid. This implementation check that it has a short name or a long name. - * This method is intended to be extended by sub-class. Parser should check that the set of - * option of a {@link CLI} is valid before starting the parsing. - *

- * If the configuration is not valid, this method throws a {@link IllegalArgumentException}. - */ - public void ensureValidity() { - if ((shortName == null || shortName.equals(NO_NAME)) && (longName == null || longName.equals(NO_NAME))) { - throw new IllegalArgumentException("An option needs at least a long name or a short name"); - } - } - - /** - * @return whether or not the option can receive a value. - */ - public boolean acceptValue() { - return singleValued || multiValued; - } - - /** - * @return the option name. It returns the long name if set, the short name otherwise. It cannot return {@code - * null} for valid option - * @see #ensureValidity() - */ - public String getName() { - if (longName != null && !longName.equals(NO_NAME)) { - return longName; - } - // So by validity, it necessarily has a short name. - return shortName; - } - - /** - * @return whether or not this option can receive several values. - */ - public boolean isMultiValued() { - return multiValued; - } - - /** - * Sets whether or not this option can receive several values. - * - * @param multiValued whether or not this option is multi-valued. - * @return the current {@link Option} instance - */ - public Option setMultiValued(boolean multiValued) { - this.multiValued = multiValued; - if (this.multiValued) { - // If we accept more than one, we also accept 1. - this.singleValued = true; - } - // Else we cannot say what is the desired value of singleValued, it needs to be explicitly set. - return this; - } - - /** - * @return whether or not this option is single valued. - */ - public boolean isSingleValued() { - return singleValued; - } - - /** - * Sets whether or not this option can receive a value. - * - * @param singleValued whether or not this option is single-valued. - * @return the current {@link Option} instance - */ - public Option setSingleValued(boolean singleValued) { - this.singleValued = singleValued; - return this; - } - - /** - * @return the option arg name used in usage messages, {@code null} if not set. - */ - public String getArgName() { - return argName; - } - - /** - * Sets te arg name for this option. - * - * @param argName the arg name, must not be {@code null} - * @return the current {@link Option} instance - */ - public Option setArgName(String argName) { - Objects.requireNonNull(argName); - this.argName = argName; - return this; - } - - /** - * @return the description of this option, {@code null} if not set. - */ - public String getDescription() { - return description; - } - - /** - * Sets te description of this option. - * - * @param description the description - * @return the current {@link Option} instance - */ - public Option setDescription(String description) { - this.description = description; - return this; - } - - /** - * @return whtehr or not this option is hidden. - */ - public boolean isHidden() { - return hidden; - } - - /** - * Sets whether or not this option should be hidden - * - * @param hidden {@code true} to make this option hidden, {@link false} otherwise - * @return the current {@link Option} instance - */ - public Option setHidden(boolean hidden) { - this.hidden = hidden; - return this; - } - - /** - * @return the option long name, {@code null} if not set. - */ - public String getLongName() { - return longName; - } - - /** - * Sets the long name of this option. - * - * @param longName the long name - * @return the current {@link Option} instance - */ - public Option setLongName(String longName) { - this.longName = longName; - return this; - } - - /** - * @return whether or not this option is mandatory. - */ - public boolean isRequired() { - return required; - } - - /** - * Sets whether or not this option is mandatory. - * - * @param required {@code true} to make this option mandatory, {@link false} otherwise - * @return the current {@link Option} instance - */ - public Option setRequired(boolean required) { - this.required = required; - return this; - } - - /** - * @return the short name of this option, {@code null} if not set. - */ - public String getShortName() { - return shortName; - } - - /** - * Sets the short name of this option. - * - * @param shortName the short name - * @return the current {@link Option} instance - */ - public Option setShortName(String shortName) { - this.shortName = shortName; - return this; - } - - /** - * @return the default value of this option, {@code null} if not set. - */ - public String getDefaultValue() { - return defaultValue; - } - - /** - * Sets the default value of this option - * - * @param defaultValue the default value - * @return the current {@link Option} instance - */ - public Option setDefaultValue(String defaultValue) { - this.defaultValue = defaultValue; - if (this.defaultValue != null) { - setRequired(false); - } - return this; - } - - /** - * @return whether or not this option is a flag. - */ - public boolean isFlag() { - return flag; - } - - /** - * Configures the current {@link Option} to be a flag. It will be evaluated to {@code true} if it's found in - * the command line. If you need a flag that may receive a value, use, in this order: - *

-   *   option.setFlag(true).setSingleValued(true)
-   * 
- * - * @param flag whether or not the option is a flag. - * @return the current {@link Option} - */ - public Option setFlag(boolean flag) { - this.flag = flag; - if (flag) { - setSingleValued(false); - } - return this; - } - - /** - * Checks whether or not this option is a "Help" option. - * - * @return {@code true} if this option is a "help" option. - */ - public boolean isHelp() { - return help; - } - - /** - * Sets whether or not this option is a "help" option - * - * @param help {@code true} to set this option as a "Help" option - * @return the current {@link Option} - */ - public Option setHelp(boolean help) { - this.help = help; - return this; - } - - /** - * @return get the list of choices for the given option. Empty if this option does not define choices. - */ - public Set getChoices() { - return choices; - } - - /** - * Sets the list of values accepted by this option. If the value set by the user does not match once of these - * values, a {@link InvalidValueException} exception is thrown. - * - * @param choices the choices - * @return the current {@link Option} - */ - public Option setChoices(Set choices) { - this.choices = choices; - return this; - } - - /** - * Adds a choice to the list of values accepted by this option. If the value set by the user does not match once of these - * values, a {@link InvalidValueException} exception is thrown. - * - * @param choice the choice - * @return the current {@link Option} - */ - public Option addChoice(String choice) { - this.choices.add(choice); - return this; - } -} diff --git a/src/main/java/io/vertx/core/cli/TypedArgument.java b/src/main/java/io/vertx/core/cli/TypedArgument.java deleted file mode 100644 index 25f8c16aab2..00000000000 --- a/src/main/java/io/vertx/core/cli/TypedArgument.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli; - -import io.vertx.core.cli.converters.Converter; - -/** - * An implementation of {@link Argument} for java specifying the type of object received by the argument. This - * allows converting the given raw value into the specified type. - * - * @author Clement Escoffier - */ -public class TypedArgument extends Argument { - - /** - * The type of the argument. - */ - protected Class type; - - /** - * The converter to use to create the value. - */ - protected Converter converter; - - /** - * Creates a new instance of {@link TypedArgument} by copying the state of another {@link TypedArgument}. - * - * @param arg the copied argument - */ - public TypedArgument(TypedArgument arg) { - super(arg); - this.type = arg.getType(); - this.converter = arg.getConverter(); - } - - /** - * Creates an empty instance of {@link TypedArgument}. - */ - public TypedArgument() { - super(); - } - - /** - * @return the argument type, cannot be {@code null} for valid argument. - */ - public Class getType() { - return type; - } - - /** - * Sets the argument type. - * - * @param type the type - * @return the current {@link TypedArgument} instance - */ - public TypedArgument setType(Class type) { - this.type = type; - return this; - } - - /** - * @return the converter used to create the value, {@code null} if not set - */ - public Converter getConverter() { - return converter; - } - - /** - * Sets the converter used to create the value. - * - * @param converter the converter - * @return the current {@link TypedArgument} instance - */ - public TypedArgument setConverter(Converter converter) { - this.converter = converter; - return this; - } - - /** - * Checks whether or not the argument configuration is valid. In addition of the check made by the parent class it - * ensures that the type is set. - * If the configuration is not valid, this method throws a {@link IllegalArgumentException}. - */ - @Override - public void ensureValidity() { - super.ensureValidity(); - if (type == null) { - throw new IllegalArgumentException("Type must not be null"); - } - } - - @Override - public TypedArgument setArgName(String argName) { - super.setArgName(argName); - return this; - } - - @Override - public TypedArgument setDefaultValue(String defaultValue) { - super.setDefaultValue(defaultValue); - return this; - } - - @Override - public TypedArgument setDescription(String description) { - super.setDescription(description); - return this; - } - - @Override - public TypedArgument setHidden(boolean hidden) { - super.setHidden(hidden); - return this; - } - - @Override - public TypedArgument setIndex(int index) { - super.setIndex(index); - return this; - } - - @Override - public TypedArgument setRequired(boolean required) { - super.setRequired(required); - return this; - } - - @Override - public TypedArgument setMultiValued(boolean multiValued) { - super.setMultiValued(multiValued); - return this; - } -} diff --git a/src/main/java/io/vertx/core/cli/TypedOption.java b/src/main/java/io/vertx/core/cli/TypedOption.java deleted file mode 100644 index e840f7db830..00000000000 --- a/src/main/java/io/vertx/core/cli/TypedOption.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli; - -import io.vertx.core.cli.converters.Converter; - -import java.util.Arrays; -import java.util.Objects; -import java.util.Set; - -/** - * An implementation of {@link Option} for java specifying the type of - * object received by the option. This allows converting the given raw value into the specified type. - * - * @author Clement Escoffier - */ -public class TypedOption extends Option { - - /** - * The type of the option. - */ - protected Class type; - - /** - * whether or not the raw value should be parsed as a list. The list if computed by splitting the value. - */ - protected boolean parsedAsList; - - /** - * the split character used if the raw value needs to be parsed as a list. {@code ','} is used by default. - */ - protected String listSeparator = ","; - - /** - * the converter to create the value. - */ - protected Converter converter; - - /** - * Creates an empty instance of {@link TypedOption}. - */ - public TypedOption() { - super(); - } - - /** - * Creates an instance of {@link TypedOption} by copying the state of another {@link TypedOption} - * - * @param option the copied option - */ - public TypedOption(TypedOption option) { - super(option); - this.type = option.getType(); - this.converter = option.getConverter(); - this.parsedAsList = option.isParsedAsList(); - this.listSeparator = option.getListSeparator(); - } - - @Override - public TypedOption setMultiValued(boolean acceptMultipleValues) { - super.setMultiValued(acceptMultipleValues); - return this; - } - - @Override - public TypedOption setSingleValued(boolean acceptSingleValue) { - super.setSingleValued(acceptSingleValue); - return this; - } - - @Override - public TypedOption setArgName(String argName) { - super.setArgName(argName); - return this; - } - - @Override - public TypedOption setDefaultValue(String defaultValue) { - super.setDefaultValue(defaultValue); - return this; - } - - @Override - public TypedOption setDescription(String description) { - super.setDescription(description); - return this; - } - - @Override - public TypedOption setFlag(boolean flag) { - super.setFlag(flag); - return this; - } - - @Override - public TypedOption setHidden(boolean hidden) { - super.setHidden(hidden); - return this; - } - - @Override - public TypedOption setLongName(String longName) { - super.setLongName(longName); - return this; - } - - @Override - public TypedOption setRequired(boolean required) { - super.setRequired(required); - return this; - } - - @Override - public TypedOption setShortName(String shortName) { - super.setShortName(shortName); - return this; - } - - public Class getType() { - return type; - } - - public TypedOption setType(Class type) { - this.type = type; - if (type != null && getChoices().isEmpty() && type.isEnum()) { - setChoicesFromEnumType(); - } - return this; - } - - public boolean isParsedAsList() { - return parsedAsList; - } - - public TypedOption setParsedAsList(boolean isList) { - this.parsedAsList = isList; - return this; - } - - public String getListSeparator() { - return listSeparator; - } - - public TypedOption setListSeparator(String listSeparator) { - Objects.requireNonNull(listSeparator); - this.parsedAsList = true; - this.listSeparator = listSeparator; - return this; - } - - public Converter getConverter() { - return converter; - } - - public TypedOption setConverter(Converter converter) { - this.converter = converter; - return this; - } - - @Override - public void ensureValidity() { - super.ensureValidity(); - if (type == null) { - throw new IllegalArgumentException("Type must not be null"); - } - } - - @Override - public TypedOption setChoices(Set choices) { - super.setChoices(choices); - return this; - } - - @Override - public TypedOption addChoice(String choice) { - super.addChoice(choice); - return this; - } - - /** - * Sets the list of values accepted by this option from the option's type. - */ - private void setChoicesFromEnumType() { - Object[] constants = type.getEnumConstants(); - for (Object c : constants) { - addChoice(c.toString()); - } - } - - -} diff --git a/src/main/java/io/vertx/core/cli/UsageMessageFormatter.java b/src/main/java/io/vertx/core/cli/UsageMessageFormatter.java deleted file mode 100644 index 5703d4505e8..00000000000 --- a/src/main/java/io/vertx/core/cli/UsageMessageFormatter.java +++ /dev/null @@ -1,705 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.StringReader; -import java.util.*; -import java.util.stream.Collectors; - - -/** - * Usage message formatter. - * - * @author Clement Escoffier - */ -public class UsageMessageFormatter { - - /** - * default number of characters per line - */ - public static final int DEFAULT_WIDTH = 80; - - /** - * default padding to the left of each line - */ - public static final int DEFAULT_LEFT_PAD = 1; - - /** - * number of space characters to be prefixed to each description line - */ - public static final int DEFAULT_DESC_PAD = 3; - - /** - * the string to display at the beginning of the usage statement - */ - public static final String DEFAULT_USAGE_PREFIX = "Usage: "; - - /** - * default prefix for shortOpts - */ - public static final String DEFAULT_OPT_PREFIX = "-"; - - /** - * default prefix for long Option - */ - public static final String DEFAULT_LONG_OPT_PREFIX = "--"; - - /** - * default separator displayed between a long Option and its value - */ - public static final String DEFAULT_LONG_OPT_SEPARATOR = " "; - - /** - * default name for an argument - */ - public static final String DEFAULT_ARG_NAME = "arg"; - - private int width = DEFAULT_WIDTH; - private int leftPad = DEFAULT_LEFT_PAD; - private int descPad = DEFAULT_DESC_PAD; - private String usagePrefix = DEFAULT_USAGE_PREFIX; - private String newLine = System.lineSeparator(); - private String defaultOptionPrefix = DEFAULT_OPT_PREFIX; - private String defaultLongOptPrefix = DEFAULT_LONG_OPT_PREFIX; - private String defaultArgName = DEFAULT_ARG_NAME; - private String longOptSeparator = DEFAULT_LONG_OPT_SEPARATOR; - - /** - * Comparator used to sort the options when they output in help text - *

- * Defaults to case-insensitive alphabetical sorting by option key. - */ - protected Comparator

- * Index is mandatory to force you to think to the order. - */ - int index(); - - /** - * Whether or not the argument is required. An argument is required by default. - */ - boolean required() default true; -} diff --git a/src/main/java/io/vertx/core/cli/annotations/CLIConfigurator.java b/src/main/java/io/vertx/core/cli/annotations/CLIConfigurator.java deleted file mode 100644 index f642265b5eb..00000000000 --- a/src/main/java/io/vertx/core/cli/annotations/CLIConfigurator.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.annotations; - -import io.vertx.core.cli.*; -import io.vertx.core.cli.impl.DefaultCLI; -import io.vertx.core.cli.impl.ReflectionUtils; - -import java.lang.reflect.Array; -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -/** - * Class responsible for defining CLI using annotations and injecting values extracted by the parser. - * - * @author Clement Escoffier - */ -public class CLIConfigurator { - - - /** - * Creates an instance of the given class, and extracts the metadata from the given class. - * - * @param clazz the CLI class - * @return the defined CLI. - */ - public static CLI define(Class clazz) { - CLI cli = new DefaultCLI(); - - // Class annotations - final Summary summary = clazz.getAnnotation(Summary.class); - final Description desc = clazz.getAnnotation(Description.class); - final Hidden hidden = clazz.getAnnotation(Hidden.class); - final Name name = clazz.getAnnotation(Name.class); - - if (name == null) { - throw new IllegalArgumentException("The command cannot be defined, the @Name annotation is missing."); - } - if (name.value().isEmpty()) { - throw new IllegalArgumentException("The command cannot be defined, the @Name value is empty or null."); - } - cli.setName(name.value()); - cli.setPriority(name.priority()); - - if (summary != null) { - cli.setSummary(summary.value()); - } - if (desc != null) { - cli.setDescription(desc.value()); - } - if (hidden != null) { - cli.setHidden(true); - } - - // Setter annotations - final List methods = ReflectionUtils.getSetterMethods(clazz); - for (Method method : methods) { - final Option option = method.getAnnotation(Option.class); - final Argument argument = method.getAnnotation(Argument.class); - - if (option != null) { - cli.addOption(createOption(method)); - } - if (argument != null) { - cli.addArgument(createArgument(method)); - } - } - - return cli; - } - - @SuppressWarnings("unchecked") - private static io.vertx.core.cli.Option createOption(Method method) { - TypedOption opt = new TypedOption(); - - // Option - Option option = method.getAnnotation(Option.class); - opt.setLongName(option.longName()) - .setShortName(option.shortName()) - .setMultiValued(option.acceptMultipleValues()) - .setSingleValued(option.acceptValue()) - .setArgName(option.argName()) - .setFlag(option.flag()) - .setHelp(option.help()) - .setRequired(option.required()); - - // Description - Description description = method.getAnnotation(Description.class); - if (description != null) { - opt.setDescription(description.value()); - } - - Hidden hidden = method.getAnnotation(Hidden.class); - if (hidden != null) { - opt.setHidden(true); - } - - if (ReflectionUtils.isMultiple(method)) { - opt - .setType(ReflectionUtils.getComponentType(method.getParameters()[0])) - .setMultiValued(true); - } else { - final Class type = method.getParameters()[0].getType(); - opt.setType(type); - if (type != Boolean.TYPE && type != Boolean.class) { - // In the case of a boolean, it may be a flag, need explicit settings. - opt.setSingleValued(true); - } - } - - ConvertedBy convertedBy = method.getAnnotation(ConvertedBy.class); - if (convertedBy != null) { - opt.setConverter(ReflectionUtils.newInstance(convertedBy.value())); - } - ParsedAsList parsedAsList = method.getAnnotation(ParsedAsList.class); - if (parsedAsList != null) { - opt.setParsedAsList(true).setListSeparator(parsedAsList.separator()); - } - - // Default value - DefaultValue defaultValue = method.getAnnotation(DefaultValue.class); - if (defaultValue != null) { - opt.setDefaultValue(defaultValue.value()); - } - - opt.ensureValidity(); - - return opt; - } - - @SuppressWarnings("unchecked") - private static io.vertx.core.cli.Argument createArgument(Method method) { - TypedArgument arg = new TypedArgument(); - - // Argument - Argument argument = method.getAnnotation(Argument.class); - arg.setIndex(argument.index()); - arg.setArgName(argument.argName()); - arg.setRequired(argument.required()); - - // Description - Description description = method.getAnnotation(Description.class); - if (description != null) { - arg.setDescription(description.value()); - } - - if (ReflectionUtils.isMultiple(method)) { - arg - .setType(ReflectionUtils.getComponentType(method.getParameters()[0])) - .setMultiValued(true); - } else { - final Class type = method.getParameters()[0].getType(); - arg.setType(type); - } - - Hidden hidden = method.getAnnotation(Hidden.class); - if (hidden != null) { - arg.setHidden(true); - } - - ConvertedBy convertedBy = method.getAnnotation(ConvertedBy.class); - if (convertedBy != null) { - arg.setConverter(ReflectionUtils.newInstance(convertedBy.value())); - } - - // Default value - DefaultValue defaultValue = method.getAnnotation(DefaultValue.class); - if (defaultValue != null) { - arg.setDefaultValue(defaultValue.value()); - } - - return arg; - } - - private static Object getOptionValue(Method method, String name, CommandLine commandLine) { - final io.vertx.core.cli.Option option = commandLine.cli().getOption(name); - if (option == null) { - return null; - } - boolean multiple = ReflectionUtils.isMultiple(method); - if (multiple) { - return createMultiValueContainer(method, commandLine.getOptionValues(name)); - } - return commandLine.getOptionValue(name); - } - - private static Object getArgumentValue(Method method, int index, CommandLine commandLine) { - final io.vertx.core.cli.Argument argument = commandLine.cli().getArgument(index); - if (argument == null) { - return null; - } - - boolean multiple = ReflectionUtils.isMultiple(method); - if (multiple) { - return createMultiValueContainer(method, commandLine.getArgumentValues(argument.getIndex())); - } - return commandLine.getArgumentValue(argument.getIndex()); - } - - /** - * Injects the value in the annotated setter methods ({@link Option} and {@link Argument}. - * - * @param cli the cli - * @param object the object to be injected - * @throws CLIException if an injection issue occurred. - */ - public static void inject(CommandLine cli, Object object) throws CLIException { - final List methods = ReflectionUtils.getSetterMethods(object.getClass()); - for (Method method : methods) { - Option option = method.getAnnotation(Option.class); - Argument argument = method.getAnnotation(Argument.class); - if (option != null) { - String name = option.longName(); - if (name == null) { - name = option.shortName(); - } - try { - Object injected = getOptionValue(method, name, cli); - if (injected != null) { - method.setAccessible(true); - method.invoke(object, injected); - } - } catch (Exception e) { - throw new CLIException("Cannot inject value for option '" + name + "'", e); - } - } - - if (argument != null) { - int index = argument.index(); - try { - Object injected = getArgumentValue(method, index, cli); - if (injected != null) { - method.setAccessible(true); - method.invoke(object, injected); - } - } catch (Exception e) { - throw new CLIException("Cannot inject value for argument '" + index + "'", e); - } - } - - } - } - - private static Object createMultiValueContainer(Method setter, List values) { - final Class type = setter.getParameterTypes()[0]; - if (type.isArray()) { - Object array = Array.newInstance(type.getComponentType(), values.size()); - for (int i = 0; i < values.size(); i++) { - Array.set(array, i, values.get(i)); - } - return array; - } - - if (Set.class.isAssignableFrom(type)) { - return new LinkedHashSet<>(values); - } - - if (List.class.isAssignableFrom(type) || Collection.class.isAssignableFrom(type)) { - return values; - } - - return null; - } - -} diff --git a/src/main/java/io/vertx/core/cli/annotations/ConvertedBy.java b/src/main/java/io/vertx/core/cli/annotations/ConvertedBy.java deleted file mode 100644 index 62085bbf3e6..00000000000 --- a/src/main/java/io/vertx/core/cli/annotations/ConvertedBy.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.annotations; - -import io.vertx.core.cli.converters.Converter; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotates {@link Option @Option} setters to indicate how the value is converted to the argument type. - * - * @author Clement Escoffier - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.TYPE}) -public @interface ConvertedBy { - - /** - * The converter class used to transform the value as String to the target type. This converter is also used for - * the {@link DefaultValue}. - */ - Class> value(); - -} diff --git a/src/main/java/io/vertx/core/cli/annotations/DefaultValue.java b/src/main/java/io/vertx/core/cli/annotations/DefaultValue.java deleted file mode 100644 index 463ed2ee8fa..00000000000 --- a/src/main/java/io/vertx/core/cli/annotations/DefaultValue.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.annotations; - -import io.vertx.core.cli.converters.Converter; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation to set a default value to an option. - * - * @author Clement Escoffier - * @see Option - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface DefaultValue { - - /** - * The (optional) default value of the option. The value is converted to the right type using the - * {@link Converter} set in {@link ConvertedBy}. - */ - String value(); -} diff --git a/src/main/java/io/vertx/core/cli/annotations/Description.java b/src/main/java/io/vertx/core/cli/annotations/Description.java deleted file mode 100644 index 227b9602e50..00000000000 --- a/src/main/java/io/vertx/core/cli/annotations/Description.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.annotations; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotation used to write the option or command documentation. - * - * @author Clement Escoffier - * @see Summary - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.TYPE}) -public @interface Description { - - /** - * The documentation. - */ - String value(); -} diff --git a/src/main/java/io/vertx/core/cli/annotations/Hidden.java b/src/main/java/io/vertx/core/cli/annotations/Hidden.java deleted file mode 100644 index 058218277f9..00000000000 --- a/src/main/java/io/vertx/core/cli/annotations/Hidden.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.annotations; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotates a {@link io.vertx.core.cli.CLI} and/or its {@link Option @Option} setters to hide it from the help message. - * - * @author Clement Escoffier - * @see Summary - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.TYPE}) -public @interface Hidden { - // Just a marker. -} diff --git a/src/main/java/io/vertx/core/cli/annotations/Name.java b/src/main/java/io/vertx/core/cli/annotations/Name.java deleted file mode 100644 index aeb4ce59ba1..00000000000 --- a/src/main/java/io/vertx/core/cli/annotations/Name.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.annotations; - -import io.vertx.core.cli.CLI; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Defines the name of a {@link CLI}. - * - * @author Clement Escoffier - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface Name { - - /** - * The command name. - */ - String value(); - - /** - * The command priority. If more than 1 with same name are available on the classpath the one with highest priority - * replaces the existing. - */ - int priority() default 0; -} diff --git a/src/main/java/io/vertx/core/cli/annotations/Option.java b/src/main/java/io/vertx/core/cli/annotations/Option.java deleted file mode 100644 index 70b7ce40f82..00000000000 --- a/src/main/java/io/vertx/core/cli/annotations/Option.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.annotations; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotates a setter to be called with the value of a command line option. Setter have been preferred to field to - * allow validation. - *

- * The cardinality of the option is detected from the single method parameter type: arrays, list and set can receive - * several values. - * - * @author Clement Escoffier - * @see Argument - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface Option { - - String NO_NAME = "\0"; - - /** - * The name of the option (without the {@code --} prefix). - * Defaults to a name based on the setter name - */ - String longName() default NO_NAME; - - /** - * The short option name (without the {@code -} prefix). - * If not given the option has no short name. - */ - String shortName() default NO_NAME; - - /** - * The name of this argument (used in doc) - */ - String argName() default "value"; - - /** - * Whether or not the option is required. - */ - boolean required() default false; - - /** - * Whether or not the option accept a value. - * If the setter accepts an array, a list, a set, or a collection as parameter, it automatically detect it accepts - * multiple values. - */ - boolean acceptValue() default true; - - /** - * Whether or not the option accept multiple values. If the setter accepts an array, a list, a set, or a collection - * as parameter, it automatically detect it accepts multiple values. - */ - boolean acceptMultipleValues() default false; - - /** - * Whether or not the option can be used as a flag (meaning no value) - */ - boolean flag() default false; - - /** - * Whether or not this option is a "Help" option. Help options are generally flag. - */ - boolean help() default false; - - /** - * The set of choices accepted as values by this option. No need to call this methods for enums. - */ - String[] choices() default {}; -} diff --git a/src/main/java/io/vertx/core/cli/annotations/ParsedAsList.java b/src/main/java/io/vertx/core/cli/annotations/ParsedAsList.java deleted file mode 100644 index 808f31e0f94..00000000000 --- a/src/main/java/io/vertx/core/cli/annotations/ParsedAsList.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.annotations; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotates a setter to be called with the value of a command line option. The setter must also have been annotated - * with {@link Option}. - *

- * When annotated with {@link ParsedAsList}, the option value is parsed as a list. The value is split and then each - * segment is trimmed. - * - * @author Clement Escoffier - * @see Option - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface ParsedAsList { - - /** - * The separator used to split the value. {@code ,} is used by default. - */ - String separator() default ","; -} diff --git a/src/main/java/io/vertx/core/cli/annotations/Summary.java b/src/main/java/io/vertx/core/cli/annotations/Summary.java deleted file mode 100644 index f2e0743a25e..00000000000 --- a/src/main/java/io/vertx/core/cli/annotations/Summary.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.annotations; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Annotates a {@link io.vertx.core.cli.CLI} with summary. The summary is the main short explanation of the command. Long - * description should be written in the {@link Description}. - * - * @author Clement Escoffier - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface Summary { - - /** - * The summary. This should be a single sentence describing what the command does. - */ - String value(); - -} diff --git a/src/main/java/io/vertx/core/cli/converters/BooleanConverter.java b/src/main/java/io/vertx/core/cli/converters/BooleanConverter.java deleted file mode 100644 index 25eee3dee4b..00000000000 --- a/src/main/java/io/vertx/core/cli/converters/BooleanConverter.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.converters; - -import java.util.Arrays; -import java.util.List; - -/** - * A converter for boolean. This converter considered as 'true' : "true", "on", "1", - * "yes". All other values are considered as 'false' (as a consequence, 'null' is considered as 'false'). - * - * @author Clement Escoffier - */ -public final class BooleanConverter implements Converter { - - /** - * The converter. - */ - public static final BooleanConverter INSTANCE = new BooleanConverter(); - /** - * The set of values considered as 'true'. - */ - private static final List TRUE = Arrays.asList("true", "yes", "on", "1"); - - private BooleanConverter() { - // No direct instantiation - } - - /** - * Creates the boolean value from the given String. If the given String does not match one of the 'true' value, - * {@code false} is returned. - * - * @param value the value - * @return the boolean object - */ - @Override - public Boolean fromString(String value) { - return value != null && TRUE.contains(value.toLowerCase()); - } -} diff --git a/src/main/java/io/vertx/core/cli/converters/CharacterConverter.java b/src/main/java/io/vertx/core/cli/converters/CharacterConverter.java deleted file mode 100644 index 59ba6de3f0d..00000000000 --- a/src/main/java/io/vertx/core/cli/converters/CharacterConverter.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.converters; - - -/** - * A converter for character. Unlike other primitive types, characters cannot be created using 'valueOf'. Notice that - * only input having a length of 1 can be converted to characters. Other inputs are rejected. - * - * @author Clement Escoffier - */ -public final class CharacterConverter implements Converter { - - /** - * The converter. - */ - public static final CharacterConverter INSTANCE = new CharacterConverter(); - - private CharacterConverter() { - // No direct instantiation - } - - @Override - public Character fromString(String input) throws IllegalArgumentException { - if (input == null) { - throw new NullPointerException("input must not be null"); - } - - if (input.length() != 1) { - throw new IllegalArgumentException("The input string \"" + input + "\" cannot be converted to a " + - "character. The input's length must be 1"); - } - - return input.toCharArray()[0]; - - } -} diff --git a/src/main/java/io/vertx/core/cli/converters/ConstructorBasedConverter.java b/src/main/java/io/vertx/core/cli/converters/ConstructorBasedConverter.java deleted file mode 100644 index 699d704f12b..00000000000 --- a/src/main/java/io/vertx/core/cli/converters/ConstructorBasedConverter.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.converters; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; - -/** - * This 'default' converter tries to create objects using a constructor taking a single String argument. - * Be aware that implementation must also handle the case where the input is {@literal null}. - * - * @author Clement Escoffier - */ -public final class ConstructorBasedConverter implements Converter { - - private final Constructor constructor; - - private ConstructorBasedConverter(Constructor constructor) { - this.constructor = constructor; - } - - /** - * Checks whether the given class can be used by the {@link ConstructorBasedConverter} (i.e. has a constructor - * taking a single String as argument). If so, creates a new instance of converter for this type. - * - * @param clazz the class - * @return a {@link ConstructorBasedConverter} if the given class is eligible, - * {@literal null} otherwise. - */ - public static ConstructorBasedConverter getIfEligible(Class clazz) { - try { - final Constructor constructor = clazz.getConstructor(String.class); - if (!constructor.isAccessible()) { - constructor.setAccessible(true); - } - return new ConstructorBasedConverter<>(constructor); - } catch (NoSuchMethodException e) { - // The class does not have the right constructor, return null. - return null; - } - - } - - /** - * Converts the given input to an object by using the constructor approach. Notice that the constructor must - * expect receiving a {@literal null} value. - * - * @param input the input, can be {@literal null} - * @return the instance of T - * @throws IllegalArgumentException if the instance of T cannot be created from the input. - */ - @Override - public T fromString(String input) throws IllegalArgumentException { - try { - return constructor.newInstance(input); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - if (e.getCause() != null) { - throw new IllegalArgumentException(e.getCause()); - } else { - throw new IllegalArgumentException(e); - } - } - } - -} diff --git a/src/main/java/io/vertx/core/cli/converters/Converters.java b/src/main/java/io/vertx/core/cli/converters/Converters.java deleted file mode 100644 index 8730d866319..00000000000 --- a/src/main/java/io/vertx/core/cli/converters/Converters.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.converters; - -import java.lang.reflect.InvocationTargetException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.NoSuchElementException; - -/** - * Entry point to the converter system. - * - * @author Clement Escoffier - */ -public class Converters { - - private static final Map, Class> PRIMITIVE_TO_WRAPPER_TYPE; - private static final Map, Converter> WELL_KNOWN_CONVERTERS; - - static { - Map, Class> primToWrap = new HashMap<>(16); - - primToWrap.put(boolean.class, Boolean.class); - primToWrap.put(byte.class, Byte.class); - primToWrap.put(char.class, Character.class); - primToWrap.put(double.class, Double.class); - primToWrap.put(float.class, Float.class); - primToWrap.put(int.class, Integer.class); - primToWrap.put(long.class, Long.class); - primToWrap.put(short.class, Short.class); - primToWrap.put(void.class, Void.class); - - PRIMITIVE_TO_WRAPPER_TYPE = Collections.unmodifiableMap(primToWrap); - - Map, Converter> wellKnown = new HashMap<>(16); - wellKnown.put(Boolean.class, BooleanConverter.INSTANCE); - wellKnown.put(Byte.class, Byte::parseByte); - wellKnown.put(Character.class, CharacterConverter.INSTANCE); - wellKnown.put(Double.class, Double::parseDouble); - wellKnown.put(Float.class, Float::parseFloat); - wellKnown.put(Integer.class, Integer::parseInt); - wellKnown.put(Long.class, Long::parseLong); - wellKnown.put(Short.class, Short::parseShort); - wellKnown.put(String.class, value -> value); - - WELL_KNOWN_CONVERTERS = Collections.unmodifiableMap(wellKnown); - } - - public static T create(Class type, String value) { - if (type.isPrimitive()) { - type = wrap(type); - } - return getConverter(type).fromString(value); - } - - public static T create(String value, Converter converter) { - return converter.fromString(value); - } - - @SuppressWarnings("unchecked") - private static Class wrap(Class type) { - Class wrapped = (Class) PRIMITIVE_TO_WRAPPER_TYPE.get(type); - return (wrapped == null) ? type : wrapped; - } - - /** - * Searches a suitable converter to convert String to the given type. - * - * @param type the target type - * @param the class - * @return the parameter converter able to creates instances of the target type from String representations. - * @throws NoSuchElementException if no converter can be found - */ - @SuppressWarnings("unchecked") - private static Converter getConverter(Class type) { - // check for well known types first - if (WELL_KNOWN_CONVERTERS.containsKey(type)) { - return (Converter) WELL_KNOWN_CONVERTERS.get(type); - } - - // None of them are there, try default converters in the following order: - // 1. constructor - // 2. valueOf - // 3. from - // 4. fromString - Converter converter = ConstructorBasedConverter.getIfEligible(type); - if (converter != null) { - return converter; - } - converter = ValueOfBasedConverter.getIfEligible(type); - if (converter != null) { - return converter; - } - converter = FromBasedConverter.getIfEligible(type); - if (converter != null) { - return converter; - } - converter = FromStringBasedConverter.getIfEligible(type); - if (converter != null) { - return converter; - } - - // running out of converters... - throw new NoSuchElementException("Cannot find a converter able to create instance of " + type.getName()); - } - - public static Converter newInstance(Class> type) throws IllegalArgumentException { - try { - return type.getDeclaredConstructor().newInstance(); - } catch (InvocationTargetException | NoSuchMethodException | InstantiationException | IllegalAccessException e) { - throw new IllegalArgumentException("Cannot create a new instance of " + type.getName() + " - it requires an " + - "public constructor without argument", e); - } - } - - -} diff --git a/src/main/java/io/vertx/core/cli/converters/FromBasedConverter.java b/src/main/java/io/vertx/core/cli/converters/FromBasedConverter.java deleted file mode 100644 index 55c11e56f72..00000000000 --- a/src/main/java/io/vertx/core/cli/converters/FromBasedConverter.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.converters; - - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; - -/** - * This 'default' converter tries to create objects using a static 'from' method taking a single String argument. - * This converter is particularly convenient for builders. - * - * @param the built type. - * @author Clement Escoffier - */ -public final class FromBasedConverter implements Converter { - - public static final String FROM = "from"; - private final Method method; - private final Class clazz; - - private FromBasedConverter(Class clazz, Method method) { - this.clazz = clazz; - this.method = method; - } - - /** - * Checks whether the given class can be used by the {@link FromBasedConverter} (i.e. has a static 'from' method - * taking a single String as argument). If so, creates a new instance of converter for this type. - * - * @param clazz the class - * @return a {@link FromBasedConverter} if the given class is eligible, - * {@literal null} otherwise. - */ - public static FromBasedConverter getIfEligible(Class clazz) { - try { - final Method method = clazz.getMethod(FROM, String.class); - if (Modifier.isStatic(method.getModifiers())) { - if (!method.isAccessible()) { - method.setAccessible(true); - } - return new FromBasedConverter<>(clazz, method); - } else { - // The from method is present but it must be static. - return null; - } - } catch (NoSuchMethodException e) { - // The class does not have the right method, return null. - return null; - } - - } - - /** - * Converts the given input to an object by using the 'from' method. Notice that the method may - * receive a {@literal null} value. - * - * @param input the input, can be {@literal null} - * @return the instance of T - * @throws IllegalArgumentException if the instance of T cannot be created from the input. - */ - @Override - public T fromString(String input) throws IllegalArgumentException { - try { - return clazz.cast(method.invoke(null, input)); - } catch (IllegalAccessException | InvocationTargetException e) { - if (e.getCause() != null) { - throw new IllegalArgumentException(e.getCause()); - } else { - throw new IllegalArgumentException(e); - } - } - } -} diff --git a/src/main/java/io/vertx/core/cli/converters/FromStringBasedConverter.java b/src/main/java/io/vertx/core/cli/converters/FromStringBasedConverter.java deleted file mode 100644 index fc60177e6df..00000000000 --- a/src/main/java/io/vertx/core/cli/converters/FromStringBasedConverter.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.converters; - - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; - -/** - * This 'default' converter tries to create objects using a static 'from' method taking a single String argument. - * This converter is particularly convenient for converters themselves. - * - * @author Clement Escoffier - */ -public final class FromStringBasedConverter implements Converter { - - public static final String FROM_STRING = "fromString"; - private final Method method; - private final Class clazz; - - private FromStringBasedConverter(Class clazz, Method method) { - this.clazz = clazz; - this.method = method; - } - - /** - * Checks whether the given class can be used by the {@link FromStringBasedConverter} (i.e. has a static - * 'fromString' method taking a single String as argument). If so, creates a new instance of converter for this - * type. - * - * @param clazz the class - * @return a {@link FromStringBasedConverter} if the given class is eligible, - * {@literal null} otherwise. - */ - public static FromStringBasedConverter getIfEligible(Class clazz) { - try { - final Method method = clazz.getMethod(FROM_STRING, String.class); - if (Modifier.isStatic(method.getModifiers())) { - if (!method.isAccessible()) { - method.setAccessible(true); - } - return new FromStringBasedConverter<>(clazz, method); - } else { - // The from method is present but it must be static. - return null; - } - } catch (NoSuchMethodException e) { - // The class does not have the right method, return null. - return null; - } - - } - - /** - * Converts the given input to an object by using the 'fromString' method. Notice that the method may - * receive a {@literal null} value. - * - * @param input the input, can be {@literal null} - * @return the instance of T - * @throws IllegalArgumentException if the instance of T cannot be created from the input. - */ - @Override - public T fromString(String input) throws IllegalArgumentException { - try { - return clazz.cast(method.invoke(null, input)); - } catch (IllegalAccessException | InvocationTargetException e) { - if (e.getCause() != null) { - throw new IllegalArgumentException(e.getCause()); - } else { - throw new IllegalArgumentException(e); - } - } - } -} diff --git a/src/main/java/io/vertx/core/cli/converters/ValueOfBasedConverter.java b/src/main/java/io/vertx/core/cli/converters/ValueOfBasedConverter.java deleted file mode 100644 index 6221732aa72..00000000000 --- a/src/main/java/io/vertx/core/cli/converters/ValueOfBasedConverter.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.converters; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; - -/** - * This 'default' converter tries to create objects using a static 'valueOf' method taking a single String argument. - * This converter is particularly convenient to for enumeration and primitive types. - * - * @author Clement Escoffier - */ -public final class ValueOfBasedConverter implements Converter { - - public static final String VALUE_OF = "valueOf"; - private final Method method; - private final Class clazz; - - private ValueOfBasedConverter(Class clazz, Method method) { - this.clazz = clazz; - this.method = method; - } - - /** - * Checks whether the given class can be used by the {@link ValueOfBasedConverter} (i.e. has a static 'valueOf' - * method taking a single String as argument). If so, creates a new instance of converter for this type. - * - * @param clazz the class - * @return a {@link ValueOfBasedConverter} if the given class is eligible, - * {@literal null} otherwise. - */ - @SuppressWarnings("unchecked") - public static ValueOfBasedConverter getIfEligible(Class clazz) { - try { - final Method method = clazz.getMethod(VALUE_OF, String.class); - if (Modifier.isStatic(method.getModifiers())) { - if (!method.isAccessible()) { - method.setAccessible(true); - } - return new ValueOfBasedConverter(clazz, method); - } else { - // The valueOf method is present but it must be static. - return null; - } - } catch (NoSuchMethodException e) { - // The class does not have the right method, return null. - return null; - } - - } - - /** - * Converts the given input to an object by using the 'valueOf' method. Notice that the method may - * receive a {@literal null} value. - * - * @param input the input, can be {@literal null} - * @return the instance of T - * @throws IllegalArgumentException if the instance of T cannot be created from the input. - */ - @Override - public T fromString(String input) throws IllegalArgumentException { - try { - return clazz.cast(method.invoke(null, input)); - } catch (IllegalAccessException | InvocationTargetException e) { - if (e.getCause() != null) { - throw new IllegalArgumentException(e.getCause()); - } else { - throw new IllegalArgumentException(e); - } - } - } -} diff --git a/src/main/java/io/vertx/core/cli/impl/DefaultCLI.java b/src/main/java/io/vertx/core/cli/impl/DefaultCLI.java deleted file mode 100644 index a66749d5ead..00000000000 --- a/src/main/java/io/vertx/core/cli/impl/DefaultCLI.java +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.impl; - -import io.vertx.core.cli.*; - -import java.util.*; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -/** - * Default implementation of {@link CLI}. - * - * @author Clement Escoffier - */ -public class DefaultCLI implements CLI { - - protected String name; - protected int priority; - protected String description; - protected String summary; - protected boolean hidden; - - protected List

- * --L - * --L=V - * --L V - * --l - * - * @param token the command line token to handle - */ - private void handleLongOption(String token) throws CLIException { - if (token.indexOf('=') == -1) { - handleLongOptionWithoutEqual(token); - } else { - handleLongOptionWithEqual(token); - } - } - - /** - * Handles the following tokens: - *

- * --L - * -L - * --l - * -l - * - * @param token the command line token to handle - */ - private void handleLongOptionWithoutEqual(String token) throws CLIException { - List

- * --L=V - * -L=V - * --l=V - * -l=V - * - * @param token the command line token to handle - */ - private void handleLongOptionWithEqual(String token) throws CLIException { - int pos = token.indexOf('='); - - String value = token.substring(pos + 1); - - String opt = token.substring(0, pos); - - List

- * -S - * -SV - * -S V - * -S=V - *

- * -L - * -LV - * -L V - * -L=V - * -l - * - * @param token the command line token to handle - */ - private void handleShortAndLongOption(String token) throws CLIException { - String t = stripLeadingHyphens(token); - int pos = t.indexOf('='); - - if (t.length() == 1) { - // -S - if (hasOptionWithShortName(t)) { - handleOption(getOption(t)); - } else { - handleArgument(token); - } - } else if (pos == -1) { - // no equal sign found (-xxx) - if (hasOptionWithShortName(t)) { - handleOption(getOption(t)); - } else if (!getMatchingOptions(t).isEmpty()) { - // -L or -l - handleLongOptionWithoutEqual(token); - } else { - // look for a long prefix (-Xmx512m) - String opt = getLongPrefix(t); - if (opt != null) { - if (commandLine.acceptMoreValues(getOption(opt))) { - handleOption(getOption(opt)); - commandLine.addRawValue(getOption(opt), t.substring(opt.length())); - current = null; - } else { - throw new InvalidValueException(getOption(opt), t.substring(opt.length())); - } - } else if (isAValidShortOption(t)) { - // -SV1 (-Dflag) - String strip = t.substring(0, 1); - Option option = getOption(strip); - handleOption(option); - commandLine.addRawValue(current, t.substring(1)); - current = null; - } else { - // -S1S2S3 or -S1S2V - handleConcatenatedOptions(token); - } - } - } else { - // equal sign found (-xxx=yyy) - String opt = t.substring(0, pos); - String value = t.substring(pos + 1); - if (opt.length() == 1) { - // -S=V - Option option = getOption(opt); - if (option != null) { - if (commandLine.acceptMoreValues(option)) { - handleOption(option); - commandLine.addRawValue(option, value); - current = null; - } else { - throw new InvalidValueException(option, value); - } - } else { - handleArgument(token); - } - } else if (isAValidShortOption(opt) && !hasOptionWithLongName(opt)) { - // -SV1=V2 (-Dkey=value) - handleOption(getOption(opt.substring(0, 1))); - commandLine.addRawValue(current, opt.substring(1) + "=" + value); - current = null; - } else { - // -L=V or -l=V - handleLongOptionWithEqual(token); - } - } - } - - /** - * Search for a prefix that is the long name of an option (-Xmx512m) - * - * @param token the token - * @return the found prefix. - */ - private String getLongPrefix(String token) { - String t = stripLeadingHyphens(token); - - int i; - String opt = null; - for (i = t.length() - 2; i > 1; i--) { - String prefix = t.substring(0, i); - if (hasOptionWithLongName(prefix)) { - opt = prefix; - break; - } - } - return opt; - } - - private boolean hasOptionWithLongName(String name) { - for (Option option : cli.getOptions()) { - if (name.equals(option.getLongName())) { - return true; - } - } - return false; - } - - private boolean hasOptionWithShortName(String name) { - for (Option option : cli.getOptions()) { - if (name.equals(option.getShortName())) { - return true; - } - } - return false; - } - - private void handleOption(Option option) throws CLIException { - // check the previous option before handling the next one - checkRequiredValues(); - updateRequiredOptions(option); - //IMPORTANT for flag we must set this attributes as it will determine the value out of it. - commandLine.setSeenInCommandLine(option); - if (commandLine.acceptMoreValues(option)) { - current = option; - } else { - current = null; - } - } - - /** - * Removes the option from the list of expected elements. - * - * @param option the option - */ - private void updateRequiredOptions(Option option) { - if (option.isRequired()) { - expectedOpts.remove(option); - } - } - - /** - * Retrieve the {@link Option} matching the long or short name specified. - * The leading hyphens in the name are ignored (up to 2). - * - * @param opt short or long name of the {@link Option} - * @return the option represented by opt - */ - public Option getOption(String opt) { - opt = stripLeadingHyphens(opt); - for (Option option : cli.getOptions()) { - if (opt.equals(option.getShortName()) || opt.equalsIgnoreCase(option.getLongName())) { - return option; - } - } - return null; - } - - private boolean isAValidShortOption(String token) { - String opt = token.substring(0, 1); - Option option = getOption(opt); - return option != null && commandLine.acceptMoreValues(option); - } - - /** - * Returns the options with a long name starting with the name specified. - * - * @param opt the partial name of the option - * @return the options matching the partial name specified, or an empty list if none matches - */ - public List

+ * The resolver caches the hosts configuration {@link #hostsPath file} after it has read it. When + * the content of this file can change, setting a positive refresh period will load the configuration + * file again when necessary. + * + * @param hostsRefreshPeriod the hosts configuration refresh period + */ + public AddressResolverOptions setHostsRefreshPeriod(int hostsRefreshPeriod) { + if (hostsRefreshPeriod < 0) { + throw new IllegalArgumentException("hostsRefreshPeriod must be >= 0"); + } + this.hostsRefreshPeriod = hostsRefreshPeriod; + return this; + } + /** * @return the list of dns server */ diff --git a/src/main/java/io/vertx/core/dns/DnsClient.java b/src/main/java/io/vertx/core/dns/DnsClient.java index 450a8d61918..3f20d30df5d 100644 --- a/src/main/java/io/vertx/core/dns/DnsClient.java +++ b/src/main/java/io/vertx/core/dns/DnsClient.java @@ -15,12 +15,19 @@ import io.vertx.core.Future; import io.vertx.codegen.annotations.VertxGen; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.List; +import static io.vertx.core.dns.impl.DnsClientImpl.HEX_TABLE; + /** * Provides a way to asynchronously lookup information from DNS servers. *

* Please consult the documentation for more information on DNS clients. + *

+ * The client is thread safe and can be used from any thread. * * @author Norman Maurer */ @@ -137,5 +144,44 @@ public interface DnsClient { * @return a future notified with the resolved {@link String} if a record was found. If none was found it will * get notified with {@code null}. If an error occurs it will get failed. */ - Future<@Nullable String> reverseLookup(String ipaddress); + default Future<@Nullable String> reverseLookup(String ipaddress) { + try { + InetAddress inetAddress = InetAddress.getByName(ipaddress); + byte[] addr = inetAddress.getAddress(); + + StringBuilder reverseName = new StringBuilder(64); + if (inetAddress instanceof Inet4Address) { + // reverse ipv4 address + reverseName.append(addr[3] & 0xff).append(".") + .append(addr[2]& 0xff).append(".") + .append(addr[1]& 0xff).append(".") + .append(addr[0]& 0xff); + } else { + // It is an ipv 6 address time to reverse it + for (int i = 0; i < 16; i++) { + reverseName.append(HEX_TABLE[(addr[15 - i] & 0xf)]); + reverseName.append("."); + reverseName.append(HEX_TABLE[(addr[15 - i] >> 4) & 0xf]); + if (i != 15) { + reverseName.append("."); + } + } + } + reverseName.append(".in-addr.arpa"); + + return resolvePTR(reverseName.toString()); + } catch (UnknownHostException e) { + // Should never happen as we work with ip addresses as input + // anyway just in case notify the handler + return Future.failedFuture(e); + } + } + + /** + * Close the client. + * + * @return the future completed when the client resources have been released + */ + Future close(); + } diff --git a/src/main/java/io/vertx/core/dns/DnsClientOptions.java b/src/main/java/io/vertx/core/dns/DnsClientOptions.java index 7fe24694504..45e2b3b6e0d 100644 --- a/src/main/java/io/vertx/core/dns/DnsClientOptions.java +++ b/src/main/java/io/vertx/core/dns/DnsClientOptions.java @@ -12,6 +12,7 @@ package io.vertx.core.dns; import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.VertxOptions; import io.vertx.core.json.JsonObject; import io.netty.handler.logging.ByteBufFormat; @@ -21,7 +22,8 @@ * * @author Julien Viet */ -@DataObject(generateConverter = true) +@DataObject +@JsonGen(publicConverter = false) public class DnsClientOptions { /** @@ -43,7 +45,7 @@ public class DnsClientOptions { * The default log enabled = false */ public static final boolean DEFAULT_LOG_ENABLED = false; - + /** * The default ByteBufFormat is SIMPLE */ @@ -173,7 +175,7 @@ public DnsClientOptions setLogActivity(boolean logActivity) { public DnsClientOptions setActivityLogFormat(ByteBufFormat activityLogFormat) { this.activityLogFormat = activityLogFormat; return this; - } + } /** * Return whether or not recursion is desired diff --git a/src/main/java/io/vertx/core/dns/MxRecord.java b/src/main/java/io/vertx/core/dns/MxRecord.java index 75d705c6d66..e09f5d92d95 100644 --- a/src/main/java/io/vertx/core/dns/MxRecord.java +++ b/src/main/java/io/vertx/core/dns/MxRecord.java @@ -21,6 +21,11 @@ @VertxGen public interface MxRecord { + /** + * The record time to live + */ + long ttl(); + /** * The priority of the MX record. */ diff --git a/src/main/java/io/vertx/core/dns/SrvRecord.java b/src/main/java/io/vertx/core/dns/SrvRecord.java index 045a111c3f7..09140ed3538 100644 --- a/src/main/java/io/vertx/core/dns/SrvRecord.java +++ b/src/main/java/io/vertx/core/dns/SrvRecord.java @@ -27,6 +27,11 @@ public interface SrvRecord { */ int priority(); + /** + * Returns the record time to live + */ + long ttl(); + /** * Returns the weight of this service record. */ diff --git a/src/main/java/io/vertx/core/impl/resolver/DefaultResolverProvider.java b/src/main/java/io/vertx/core/dns/impl/DefaultAddressResolverProvider.java similarity index 83% rename from src/main/java/io/vertx/core/impl/resolver/DefaultResolverProvider.java rename to src/main/java/io/vertx/core/dns/impl/DefaultAddressResolverProvider.java index d3a9bbb46b0..3d25da3457e 100644 --- a/src/main/java/io/vertx/core/impl/resolver/DefaultResolverProvider.java +++ b/src/main/java/io/vertx/core/dns/impl/DefaultAddressResolverProvider.java @@ -9,21 +9,20 @@ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ -package io.vertx.core.impl.resolver; +package io.vertx.core.dns.impl; import io.netty.resolver.AddressResolverGroup; import io.netty.resolver.DefaultAddressResolverGroup; import io.vertx.core.Future; -import io.vertx.core.Handler; import io.vertx.core.dns.AddressResolverOptions; -import io.vertx.core.spi.resolver.ResolverProvider; +import io.vertx.core.spi.resolver.dns.AddressResolverProvider; import java.net.InetSocketAddress; /** * @author Julien Viet */ -public class DefaultResolverProvider implements ResolverProvider { +public class DefaultAddressResolverProvider implements AddressResolverProvider { @Override public AddressResolverGroup resolver(AddressResolverOptions options) { diff --git a/src/main/java/io/vertx/core/impl/resolver/DnsResolverProvider.java b/src/main/java/io/vertx/core/dns/impl/DnsAddressResolverProvider.java similarity index 63% rename from src/main/java/io/vertx/core/impl/resolver/DnsResolverProvider.java rename to src/main/java/io/vertx/core/dns/impl/DnsAddressResolverProvider.java index 1c57deda06f..57f10e7dd35 100644 --- a/src/main/java/io/vertx/core/impl/resolver/DnsResolverProvider.java +++ b/src/main/java/io/vertx/core/dns/impl/DnsAddressResolverProvider.java @@ -9,22 +9,20 @@ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ -package io.vertx.core.impl.resolver; +package io.vertx.core.dns.impl; -import io.netty.channel.ChannelFactory; import io.netty.channel.EventLoop; -import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.SocketChannel; import io.netty.resolver.*; import io.netty.resolver.dns.*; import io.netty.util.NetUtil; -import io.netty.util.concurrent.EventExecutor; import io.vertx.core.*; +import io.vertx.core.buffer.Buffer; import io.vertx.core.dns.AddressResolverOptions; -import io.vertx.core.impl.AddressResolver; +import io.vertx.core.impl.HostnameResolver; import io.vertx.core.impl.ContextInternal; -import io.vertx.core.impl.VertxImpl; -import io.vertx.core.spi.resolver.ResolverProvider; +import io.vertx.core.impl.VertxInternal; +import io.vertx.core.spi.resolver.dns.AddressResolverProvider; import java.io.File; import java.io.IOException; @@ -32,27 +30,33 @@ import java.net.*; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import static io.netty.util.internal.ObjectUtil.intValue; /** * @author Julien Viet */ -public class DnsResolverProvider implements ResolverProvider { +public class DnsAddressResolverProvider implements AddressResolverProvider, HostsFileEntriesResolver { - private final Vertx vertx; + public static DnsAddressResolverProvider create(VertxInternal vertx, AddressResolverOptions options) { + DnsAddressResolverProvider provider = new DnsAddressResolverProvider(vertx, options); + provider.refresh(); + return provider; + } + + private final VertxInternal vertx; private final List resolvers = Collections.synchronizedList(new ArrayList<>()); + private final DnsNameResolverBuilder dnsNameResolverBuilder; private AddressResolverGroup resolverGroup; private final List serverList = new ArrayList<>(); + private final String hostsPath; + private final Buffer hostsValue; + private final AtomicLong refreshTimestamp = new AtomicLong(); + private final long hostsRefreshPeriodNanos; + private volatile HostsFileEntries parsedHostsFile = new HostsFileEntries(Collections.emptyMap(), Collections.emptyMap()); - /** - * @return a list of DNS servers available to use - */ - public List nameServerAddresses() { - return serverList; - } - - public DnsResolverProvider(VertxImpl vertx, AddressResolverOptions options) { + private DnsAddressResolverProvider(VertxInternal vertx, AddressResolverOptions options) { List dnsServers = options.getServers(); if (dnsServers != null && dnsServers.size() > 0) { for (String dnsServer : dnsServers) { @@ -85,30 +89,8 @@ public DnsResolverProvider(VertxImpl vertx, AddressResolverOptions options) { } } DnsServerAddresses nameServerAddresses = options.isRotateServers() ? DnsServerAddresses.rotational(serverList) : DnsServerAddresses.sequential(serverList); - DnsServerAddressStreamProvider nameServerAddressProvider = hostname -> { - return nameServerAddresses.stream(); - }; + DnsServerAddressStreamProvider nameServerAddressProvider = hostname -> nameServerAddresses.stream(); - HostsFileEntries entries; - if (options.getHostsPath() != null) { - File file = vertx.resolveFile(options.getHostsPath()).getAbsoluteFile(); - try { - if (!file.exists() || !file.isFile()) { - throw new IOException(); - } - entries = HostsFileParser.parse(file); - } catch (IOException e) { - throw new VertxException("Cannot read hosts file " + file.getAbsolutePath()); - } - } else if (options.getHostsValue() != null) { - try { - entries = HostsFileParser.parse(new StringReader(options.getHostsValue().toString())); - } catch (IOException e) { - throw new VertxException("Cannot read hosts config ", e); - } - } else { - entries = HostsFileParser.parseSilently(); - } int minTtl = intValue(options.getCacheMinTimeToLive(), 0); int maxTtl = intValue(options.getCacheMaxTimeToLive(), Integer.MAX_VALUE); @@ -117,38 +99,12 @@ public DnsResolverProvider(VertxImpl vertx, AddressResolverOptions options) { DnsCache authoritativeDnsServerCache = new DefaultDnsCache(minTtl, maxTtl, negativeTtl); this.vertx = vertx; - + this.hostsPath = options.getHostsPath(); + this.hostsValue = options.getHostsValue(); + this.hostsRefreshPeriodNanos = options.getHostsRefreshPeriod(); DnsNameResolverBuilder builder = new DnsNameResolverBuilder(); - builder.hostsFileEntriesResolver(new HostsFileEntriesResolver() { - @Override - public InetAddress address(String inetHost, ResolvedAddressTypes resolvedAddressTypes) { - if (inetHost.endsWith(".")) { - inetHost = inetHost.substring(0, inetHost.length() - 1); - } - InetAddress address = lookup(inetHost, resolvedAddressTypes); - if (address == null) { - address = lookup(inetHost.toLowerCase(Locale.ENGLISH), resolvedAddressTypes); - } - return address; - } - InetAddress lookup(String inetHost, ResolvedAddressTypes resolvedAddressTypes) { - switch (resolvedAddressTypes) { - case IPV4_ONLY: - return entries.inet4Entries().get(inetHost); - case IPV6_ONLY: - return entries.inet6Entries().get(inetHost); - case IPV4_PREFERRED: - Inet4Address inet4Address = entries.inet4Entries().get(inetHost); - return inet4Address != null? inet4Address : entries.inet6Entries().get(inetHost); - case IPV6_PREFERRED: - Inet6Address inet6Address = entries.inet6Entries().get(inetHost); - return inet6Address != null? inet6Address : entries.inet4Entries().get(inetHost); - default: - throw new IllegalArgumentException("Unknown ResolvedAddressTypes " + resolvedAddressTypes); - } - } - }); + builder.hostsFileEntriesResolver(this); builder.channelFactory(() -> vertx.transport().datagramChannel()); builder.socketChannelFactory(() -> (SocketChannel) vertx.transport().channelFactory(false).newChannel()); builder.nameServerProvider(nameServerAddressProvider); @@ -158,15 +114,19 @@ InetAddress lookup(String inetHost, ResolvedAddressTypes resolvedAddressTypes) { builder.queryTimeoutMillis(options.getQueryTimeout()); builder.maxQueriesPerResolve(options.getMaxQueries()); builder.recursionDesired(options.getRdFlag()); + builder.completeOncePreferredResolved(true); + builder.consolidateCacheSize(1024); + builder.ndots(1); if (options.getSearchDomains() != null) { builder.searchDomains(options.getSearchDomains()); int ndots = options.getNdots(); if (ndots == -1) { - ndots = AddressResolver.DEFAULT_NDOTS_RESOLV_OPTION; + ndots = HostnameResolver.DEFAULT_NDOTS_RESOLV_OPTION; } builder.ndots(ndots); } + this.dnsNameResolverBuilder = builder; this.resolverGroup = new DnsAddressResolverGroup(builder) { @Override protected io.netty.resolver.AddressResolver newAddressResolver(EventLoop eventLoop, NameResolver resolver) throws Exception { @@ -182,13 +142,46 @@ protected io.netty.resolver.AddressResolver newAddressResolve }; } - private static class ResolverRegistration { - private final io.netty.resolver.AddressResolver resolver; - private final EventLoop executor; - ResolverRegistration(io.netty.resolver.AddressResolver resolver, EventLoop executor) { - this.resolver = resolver; - this.executor = executor; + @Override + public InetAddress address(String inetHost, ResolvedAddressTypes resolvedAddressTypes) { + if (inetHost.endsWith(".")) { + inetHost = inetHost.substring(0, inetHost.length() - 1); + } + if (hostsRefreshPeriodNanos > 0) { + ensureHostsFileFresh(hostsRefreshPeriodNanos); } + InetAddress address = lookup(inetHost, resolvedAddressTypes); + if (address == null) { + address = lookup(inetHost.toLowerCase(Locale.ENGLISH), resolvedAddressTypes); + } + return address; + } + InetAddress lookup(String inetHost, ResolvedAddressTypes resolvedAddressTypes) { + switch (resolvedAddressTypes) { + case IPV4_ONLY: + return parsedHostsFile.inet4Entries().get(inetHost); + case IPV6_ONLY: + return parsedHostsFile.inet6Entries().get(inetHost); + case IPV4_PREFERRED: + Inet4Address inet4Address = parsedHostsFile.inet4Entries().get(inetHost); + return inet4Address != null? inet4Address : parsedHostsFile.inet6Entries().get(inetHost); + case IPV6_PREFERRED: + Inet6Address inet6Address = parsedHostsFile.inet6Entries().get(inetHost); + return inet6Address != null? inet6Address : parsedHostsFile.inet4Entries().get(inetHost); + default: + throw new IllegalArgumentException("Unknown ResolvedAddressTypes " + resolvedAddressTypes); + } + } + + public DnsNameResolverBuilder getDnsNameResolverBuilder() { + return dnsNameResolverBuilder; + } + + /** + * @return a list of DNS servers available to use + */ + public List nameServerAddresses() { + return serverList; } @Override @@ -198,7 +191,7 @@ public AddressResolverGroup resolver(AddressResolverOptions o @Override public Future close() { - ContextInternal context = (ContextInternal) vertx.getOrCreateContext(); + ContextInternal context = vertx.getOrCreateContext(); ResolverRegistration[] registrations = this.resolvers.toArray(new ResolverRegistration[0]); if (registrations.length == 0) { return context.succeededFuture(); @@ -220,4 +213,49 @@ public Future close() { } return promise.future(); } + + public void refresh() { + ensureHostsFileFresh(0); + } + + private void ensureHostsFileFresh(long refreshPeriodNanos) { + long prev = refreshTimestamp.get(); + long now = System.nanoTime(); + if ((now - prev) >= refreshPeriodNanos && refreshTimestamp.compareAndSet(prev, now)) { + refreshHostsFile(); + } + } + + private void refreshHostsFile() { + HostsFileEntries entries; + if (hostsPath != null) { + File file = vertx.resolveFile(hostsPath).getAbsoluteFile(); + try { + if (!file.exists() || !file.isFile()) { + throw new IOException(); + } + entries = HostsFileParser.parse(file); + } catch (IOException e) { + throw new VertxException("Cannot read hosts file " + file.getAbsolutePath()); + } + } else if (hostsValue != null) { + try { + entries = HostsFileParser.parse(new StringReader(hostsValue.toString())); + } catch (IOException e) { + throw new VertxException("Cannot read hosts config ", e); + } + } else { + entries = HostsFileParser.parseSilently(); + } + parsedHostsFile = entries; + } + + private static class ResolverRegistration { + private final io.netty.resolver.AddressResolver resolver; + private final EventLoop executor; + ResolverRegistration(io.netty.resolver.AddressResolver resolver, EventLoop executor) { + this.resolver = resolver; + this.executor = executor; + } + } } diff --git a/src/main/java/io/vertx/core/dns/impl/DnsClientImpl.java b/src/main/java/io/vertx/core/dns/impl/DnsClientImpl.java index 448d90876a8..3fd79599048 100644 --- a/src/main/java/io/vertx/core/dns/impl/DnsClientImpl.java +++ b/src/main/java/io/vertx/core/dns/impl/DnsClientImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -8,292 +8,260 @@ * * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ - package io.vertx.core.dns.impl; -import io.netty.channel.*; -import io.netty.channel.socket.DatagramChannel; -import io.netty.channel.socket.InternetProtocolFamily; +import io.netty.channel.AddressedEnvelope; +import io.netty.channel.EventLoop; import io.netty.handler.codec.dns.*; -import io.netty.handler.logging.LoggingHandler; -import io.netty.util.collection.LongObjectHashMap; -import io.netty.util.collection.LongObjectMap; +import io.netty.resolver.ResolvedAddressTypes; +import io.netty.resolver.dns.DnsNameResolver; +import io.netty.resolver.dns.DnsNameResolverBuilder; +import io.netty.util.concurrent.GenericFutureListener; import io.vertx.codegen.annotations.Nullable; -import io.vertx.core.*; +import io.vertx.core.Future; +import io.vertx.core.Promise; +import io.vertx.core.VertxException; +import io.vertx.core.datagram.impl.InternetProtocolFamily; import io.vertx.core.dns.*; import io.vertx.core.dns.DnsResponseCode; -import io.vertx.core.dns.impl.decoder.RecordDecoder; import io.vertx.core.impl.ContextInternal; -import io.vertx.core.impl.future.PromiseInternal; import io.vertx.core.impl.VertxInternal; -import io.vertx.core.buffer.impl.PartialPooledByteBufAllocator; -import io.vertx.core.spi.transport.Transport; -import java.net.Inet4Address; import java.net.InetAddress; import java.net.InetSocketAddress; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.ThreadLocalRandom; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; -/** - * @author Norman Maurer - */ -public final class DnsClientImpl implements DnsClient { +public class DnsClientImpl implements DnsClient { + + public static final char[] HEX_TABLE = "0123456789abcdef".toCharArray(); - private static final char[] HEX_TABLE = "0123456789abcdef".toCharArray(); + private static final Function, @Nullable String> FIRST_OF = lst -> lst.size() > 0 ? lst.get(0) : null; private final VertxInternal vertx; - private final LongObjectMap inProgressMap = new LongObjectHashMap<>(); - private final InetSocketAddress dnsServer; - private final ContextInternal actualCtx; - private final DatagramChannel channel; + private boolean closed; + private final Map resolvers = new HashMap<>(); + private final Map ipv4Resolvers = new HashMap<>(); + private final Map ipv6Resolvers = new HashMap<>(); + private final DnsAddressResolverProvider provider; private final DnsClientOptions options; + private final Set> inflightRequests = ConcurrentHashMap.newKeySet(); public DnsClientImpl(VertxInternal vertx, DnsClientOptions options) { - Objects.requireNonNull(options, "no null options accepted"); - Objects.requireNonNull(options.getHost(), "no null host accepted"); - - this.options = new DnsClientOptions(options); - ContextInternal creatingContext = vertx.getContext(); - - this.dnsServer = new InetSocketAddress(options.getHost(), options.getPort()); - if (this.dnsServer.isUnresolved()) { + InetSocketAddress dnsServer = new InetSocketAddress(options.getHost(), options.getPort()); + if (dnsServer.isUnresolved()) { throw new IllegalArgumentException("Cannot resolve the host to a valid ip address"); } + + this.options = new DnsClientOptions(options); + this.provider = vertx.dnsAddressResolverProvider(dnsServer); this.vertx = vertx; + } - Transport transport = vertx.transport(); - actualCtx = vertx.getOrCreateContext(); - channel = transport.datagramChannel(this.dnsServer.getAddress() instanceof Inet4Address ? InternetProtocolFamily.IPv4 : InternetProtocolFamily.IPv6); - channel.config().setOption(ChannelOption.DATAGRAM_CHANNEL_ACTIVE_ON_REGISTRATION, true); - MaxMessagesRecvByteBufAllocator bufAllocator = channel.config().getRecvByteBufAllocator(); - bufAllocator.maxMessagesPerRead(1); - channel.config().setAllocator(PartialPooledByteBufAllocator.INSTANCE); - actualCtx.nettyEventLoop().register(channel); - if (options.getLogActivity()) { - channel.pipeline().addLast("logging", new LoggingHandler(options.getActivityLogFormat())); - } - channel.pipeline().addLast(new DatagramDnsQueryEncoder()); - channel.pipeline().addLast(new DatagramDnsResponseDecoder()); - channel.pipeline().addLast(new SimpleChannelInboundHandler() { - @Override - protected void channelRead0(ChannelHandlerContext ctx, DnsResponse msg) throws Exception { - DefaultDnsQuestion question = msg.recordAt(DnsSection.QUESTION); - Query query = inProgressMap.get(dnsMessageId(msg.id(), question.name())); - if (query != null) { - query.handle(msg); + private DnsNameResolver resolver(ContextInternal ctx, InternetProtocolFamily ipFamily) { + EventLoop el = ctx.nettyEventLoop(); + DnsNameResolver resolver; + synchronized (this) { + if (closed) { + return null; + } + Map resolversToUse; + ResolvedAddressTypes resolvedAddressTypes; + if (ipFamily == null) { + resolversToUse = resolvers; + resolvedAddressTypes = ResolvedAddressTypes.IPV4_PREFERRED; + } else { + switch (ipFamily) { + case IPv4: + resolversToUse = ipv4Resolvers; + resolvedAddressTypes = ResolvedAddressTypes.IPV4_ONLY; + break; + case IPv6: + resolversToUse = ipv6Resolvers; + resolvedAddressTypes = ResolvedAddressTypes.IPV6_ONLY; + break; + default: + throw new UnsupportedOperationException(); } } - }); + resolver = resolversToUse.get(el); + if (resolver == null) { + DnsNameResolverBuilder builder = provider.getDnsNameResolverBuilder(); + builder.resolvedAddressTypes(resolvedAddressTypes); + builder.queryTimeoutMillis(options.getQueryTimeout()); + builder.recursionDesired(options.isRecursionDesired()); + resolver = builder.eventLoop(el).build(); + ipv4Resolvers.put(el, resolver); + } + } + return resolver; + } + + @Override + public Future<@Nullable String> lookup(String name) { + return resolveAll(name, null) + // The DnsNameResolver does not allow to query with several questions so we need to fallback + .map(FIRST_OF); } @Override public Future<@Nullable String> lookup4(String name) { - return lookupSingle(name, DnsRecordType.A); + return resolveAll(name, InternetProtocolFamily.IPv4).map(FIRST_OF); } @Override public Future<@Nullable String> lookup6(String name) { - return lookupSingle(name, DnsRecordType.AAAA); + return resolveAll(name, InternetProtocolFamily.IPv6).map(FIRST_OF); } @Override - public Future<@Nullable String> lookup(String name) { - return lookupSingle(name, DnsRecordType.A, DnsRecordType.AAAA); + public Future> resolveA(String name) { + return queryAll(name, DnsRecordType.A, RecordDecoder.A); } @Override - public Future> resolveA(String name) { - return lookupList(name, DnsRecordType.A); + public Future> resolveAAAA(String name) { + return queryAll(name, DnsRecordType.AAAA, RecordDecoder.AAAA); } @Override public Future> resolveCNAME(String name) { - return lookupList(name, DnsRecordType.CNAME); + return queryAll(name, DnsRecordType.CNAME, RecordDecoder.DOMAIN); } @Override public Future> resolveMX(String name) { - return lookupList(name, DnsRecordType.MX); + return queryAll(name, DnsRecordType.MX, RecordDecoder.MX); } @Override public Future> resolveTXT(String name) { - return this.>lookupList(name, DnsRecordType.TXT).map(records -> { - List txts = new ArrayList<>(); - for (List txt: records) { - txts.addAll(txt); + return queryAll(name, DnsRecordType.TXT, RecordDecoder.TXT).map(lst -> { + List res = new ArrayList<>(); + for (List r : lst) { + res.addAll(r); } - return txts; + return res; }); } @Override public Future<@Nullable String> resolvePTR(String name) { - return lookupSingle(name, DnsRecordType.PTR); - } - - @Override - public Future> resolveAAAA(String name) { - return lookupList(name, DnsRecordType.AAAA); + return queryAll(name, DnsRecordType.PTR, RecordDecoder.DOMAIN).map(FIRST_OF); } @Override public Future> resolveNS(String name) { - return lookupList(name, DnsRecordType.NS); + return queryAll(name, DnsRecordType.NS, RecordDecoder.DOMAIN); } @Override public Future> resolveSRV(String name) { - return lookupList(name, DnsRecordType.SRV); + return queryAll(name, DnsRecordType.SRV, RecordDecoder.SRV); } - @Override - public Future<@Nullable String> reverseLookup(String address) { - try { - InetAddress inetAddress = InetAddress.getByName(address); - byte[] addr = inetAddress.getAddress(); - - StringBuilder reverseName = new StringBuilder(64); - if (inetAddress instanceof Inet4Address) { - // reverse ipv4 address - reverseName.append(addr[3] & 0xff).append(".") - .append(addr[2]& 0xff).append(".") - .append(addr[1]& 0xff).append(".") - .append(addr[0]& 0xff); - } else { - // It is an ipv 6 address time to reverse it - for (int i = 0; i < 16; i++) { - reverseName.append(HEX_TABLE[(addr[15 - i] & 0xf)]); - reverseName.append("."); - reverseName.append(HEX_TABLE[(addr[15 - i] >> 4) & 0xf]); - if (i != 15) { - reverseName.append("."); - } - } - } - reverseName.append(".in-addr.arpa"); - - return resolvePTR(reverseName.toString()); - } catch (UnknownHostException e) { - // Should never happen as we work with ip addresses as input - // anyway just in case notify the handler - return Future.failedFuture(e); - } - } - - private Future lookupSingle(String name, DnsRecordType... types) { - return this.lookupList(name, types).map(result -> result.isEmpty() ? null : result.get(0)); - } - - @SuppressWarnings("unchecked") - private Future> lookupList(String name, DnsRecordType... types) { + private Future> resolveAll(String name, InternetProtocolFamily ipFamily) { + Objects.requireNonNull(name); ContextInternal ctx = vertx.getOrCreateContext(); - PromiseInternal> promise = ctx.promise(); - Objects.requireNonNull(name, "no null name accepted"); - EventLoop el = actualCtx.nettyEventLoop(); - Query query = new Query(name, types); - query.promise.addListener(promise); - if (el.inEventLoop()) { - query.run(); - } else { - el.execute(query::run); + DnsNameResolver resolver = resolver(ctx, ipFamily); + if (resolver == null) { + return ctx.failedFuture("DNS client is closed"); } - return promise.future(); - } - - private long dnsMessageId(int id, String query) { - return ((long) query.hashCode() << 16) + (id & 65535); - } - - // Testing purposes - public void inProgressQueries(Handler handler) { - actualCtx.runOnContext(v -> { - handler.handle(inProgressMap.size()); + Promise> promise = ctx.promise(); + inflightRequests.add(promise); + io.netty.util.concurrent.Future> res; + res = resolver.resolveAll(name); + res.addListener((GenericFutureListener>>) future -> { + if (inflightRequests.remove(promise)) { + if (future.isSuccess()) { + promise.complete(future.getNow()); + } else { + promise.fail(future.cause()); + } + } }); + return promise + .future() + .map(addresses -> { + List ret = new ArrayList<>(); + for (InetAddress inetAddress : addresses) { + ret.add(inetAddress.getHostAddress()); + } + return ret; + }); } - private class Query { - - final DatagramDnsQuery msg; - final io.netty.util.concurrent.Promise> promise; - final String name; - final DnsRecordType[] types; - long timerID; - - public Query(String name, DnsRecordType[] types) { - this.msg = new DatagramDnsQuery(null, dnsServer, ThreadLocalRandom.current().nextInt()).setRecursionDesired(options.isRecursionDesired()); - if (!name.endsWith(".")) { - name += "."; - } - for (DnsRecordType type: types) { - msg.addRecord(DnsSection.QUESTION, new DefaultDnsQuestion(name, type, DnsRecord.CLASS_IN)); - } - this.promise = actualCtx.nettyEventLoop().newPromise(); - this.types = types; - this.name = name; - } - - private void fail(Throwable cause) { - inProgressMap.remove(dnsMessageId(msg.id(), name)); - if (timerID >= 0) { - vertx.cancelTimer(timerID); - } - promise.setFailure(cause); + private Future> queryAll(String name, DnsRecordType recordType, Function mapper) { + Objects.requireNonNull(name); + ContextInternal ctx = vertx.getOrCreateContext(); + DnsNameResolver resolver = resolver(ctx, InternetProtocolFamily.IPv4); + if (resolver == null) { + return ctx.failedFuture("DNS client is closed"); } - - void handle(DnsResponse msg) { - DnsResponseCode code = DnsResponseCode.valueOf(msg.code().intValue()); - if (code == DnsResponseCode.NOERROR) { - inProgressMap.remove(dnsMessageId(msg.id(), name)); - if (timerID >= 0) { - vertx.cancelTimer(timerID); + Promise> promise = ctx.promise(); + inflightRequests.add(promise); + io.netty.util.concurrent.Future> res = resolver.query(new DefaultDnsQuestion(name, recordType)); + res.addListener((GenericFutureListener>>) future -> { + if (inflightRequests.remove(promise)) { + if (future.isSuccess()) { + promise.complete(future.getNow()); + } else { + promise.fail(future.cause()); } - int count = msg.count(DnsSection.ANSWER); - List records = new ArrayList<>(count); - for (int idx = 0;idx < count;idx++) { - DnsRecord a = msg.recordAt(DnsSection.ANSWER, idx); - T record = RecordDecoder.decode(a); - if (isRequestedType(a.type(), types)) { - records.add(record); + } + }); + return promise + .future() + .transform(ar -> { + if (ar.succeeded()) { + AddressedEnvelope lst = ar.result(); + try { + DnsResponse content = lst.content(); + DnsResponseCode code = DnsResponseCode.valueOf(content.code().intValue()); + if (code != DnsResponseCode.NOERROR) { + return Future.failedFuture(new DnsException(code)); } + List ret = new ArrayList<>(); + int cnt = content.count(DnsSection.ANSWER); + String nameToMatch = name.endsWith(".") ? name : name + "."; + for (int i = 0;i < cnt;i++) { + DnsRecord record = content.recordAt(DnsSection.ANSWER, i); + if (record.name().equals(nameToMatch)) { + T mapped = mapper.apply(record); + if (mapped != null) { + ret.add(mapped); + } + } + } + return Future.succeededFuture(ret); + } finally { + lst.release(); } - if (records.size() > 0 && (records.get(0) instanceof MxRecordImpl || records.get(0) instanceof SrvRecordImpl)) { - Collections.sort((List) records); - } - promise.setSuccess(records); } else { - fail(new DnsException(code)); + return (Future>) (Future)ar; } - } + }); + } - void run() { - inProgressMap.put(dnsMessageId(msg.id(), name), this); - timerID = vertx.setTimer(options.getQueryTimeout(), id -> { - timerID = -1; - actualCtx.runOnContext(v -> { - fail(new VertxException("DNS query timeout for " + name)); - }); - }); - channel.writeAndFlush(msg).addListener((ChannelFutureListener) future -> { - if (!future.isSuccess()) { - actualCtx.emit(future.cause(), this::fail); + @Override + public Future close() { + synchronized (this) { + if (!closed) { + closed = true; + for (Map resolvers : Arrays.asList(resolvers, ipv4Resolvers, ipv6Resolvers)) { + Collection values = new ArrayList<>(resolvers.values()); + resolvers.clear(); + for (DnsNameResolver resolver : values) { + resolver.close(); + } } - }); - } - - private boolean isRequestedType(DnsRecordType dnsRecordType, DnsRecordType[] types) { - for (DnsRecordType t : types) { - if (t.equals(dnsRecordType)) { - return true; + for (Promise inflight : inflightRequests) { + inflight.tryFail(new VertxException("closed")); } } - return false; } + return Future.succeededFuture(); } } diff --git a/src/main/java/io/vertx/core/dns/impl/MxRecordImpl.java b/src/main/java/io/vertx/core/dns/impl/MxRecordImpl.java index b7e68bb0ea7..4c07b9c651e 100644 --- a/src/main/java/io/vertx/core/dns/impl/MxRecordImpl.java +++ b/src/main/java/io/vertx/core/dns/impl/MxRecordImpl.java @@ -16,16 +16,22 @@ /** * @author Norman Maurer */ -public final class MxRecordImpl implements MxRecord, Comparable { +final class MxRecordImpl implements MxRecord, Comparable { + private final long ttl; private final int priority; private final String name; - public MxRecordImpl(int priority, String name) { + MxRecordImpl(long ttl, int priority, String name) { + this.ttl = ttl; this.priority = priority; this.name = name; } + public long ttl() { + return ttl; + } + @Override public int priority() { return priority; diff --git a/src/main/java/io/vertx/core/dns/impl/decoder/RecordDecoder.java b/src/main/java/io/vertx/core/dns/impl/RecordDecoder.java similarity index 77% rename from src/main/java/io/vertx/core/dns/impl/decoder/RecordDecoder.java rename to src/main/java/io/vertx/core/dns/impl/RecordDecoder.java index 2d8692ee4bd..aea1f94f240 100644 --- a/src/main/java/io/vertx/core/dns/impl/decoder/RecordDecoder.java +++ b/src/main/java/io/vertx/core/dns/impl/RecordDecoder.java @@ -8,7 +8,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ -package io.vertx.core.dns.impl.decoder; +package io.vertx.core.dns.impl; import io.netty.buffer.ByteBuf; import io.netty.handler.codec.DecoderException; @@ -17,8 +17,8 @@ import io.netty.handler.codec.dns.DnsRecord; import io.netty.handler.codec.dns.DnsRecordType; import io.netty.util.CharsetUtil; -import io.vertx.core.dns.impl.MxRecordImpl; -import io.vertx.core.dns.impl.SrvRecordImpl; +import io.vertx.core.dns.MxRecord; +import io.vertx.core.dns.SrvRecord; import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; @@ -34,25 +34,30 @@ * Handles the decoding of resource records. Some default decoders are mapped to * their resource types in the map {@code decoders}. */ -public class RecordDecoder { +class RecordDecoder { private static final Logger log = LoggerFactory.getLogger(RecordDecoder.class); /** * Decodes MX (mail exchanger) resource records. */ - public static final Function MX = record -> { + static Function MX = record -> { + if (record.type() == DnsRecordType.MX) { ByteBuf packet = ((DnsRawRecord)record).content(); int priority = packet.readShort(); String name = RecordDecoder.readName(packet); - return new MxRecordImpl(priority, name); + long ttl = record.timeToLive(); + return new MxRecordImpl(ttl, priority, name); + } else { + return null; + } }; /** * Decodes any record that simply returns a domain name, such as NS (name * server) and CNAME (canonical name) resource records. */ - public static final Function DOMAIN = record -> { + static Function DOMAIN = record -> { if (record instanceof DnsPtrRecord) { String val = ((DnsPtrRecord)record).hostname(); if (val.endsWith(".")) { @@ -68,33 +73,39 @@ public class RecordDecoder { /** * Decodes A resource records into IPv4 addresses. */ - public static final Function A = address(4); + static Function A = address(DnsRecordType.A, 4); /** * Decodes AAAA resource records into IPv6 addresses. */ - public static final Function AAAA = address(16); + static Function AAAA = address(DnsRecordType.AAAA, 16); /** * Decodes SRV (service) resource records. */ - public static final Function SRV = record -> { + static Function SRV = record -> { + if (record.type() == DnsRecordType.SRV) { ByteBuf packet = ((DnsRawRecord)record).content(); int priority = packet.readShort(); int weight = packet.readShort(); int port = packet.readUnsignedShort(); + long ttl = record.timeToLive(); String target = RecordDecoder.readName(packet); String[] parts = record.name().split("\\.", 3); String service = parts[0]; String protocol = parts[1]; String name = parts[2]; - return new SrvRecordImpl(priority, weight, port, name, protocol, service, target); + return new SrvRecordImpl(ttl, priority, weight, port, name, protocol, service, target); + } else { + return null; + } }; /** * Decodes SOA (start of authority) resource records. */ - public static final Function SOA = record -> { + static Function SOA = record -> { + if (record.type() == DnsRecordType.SOA) { ByteBuf packet = ((DnsRawRecord)record).content(); String mName = RecordDecoder.readName(packet); String rName = RecordDecoder.readName(packet); @@ -104,36 +115,47 @@ public class RecordDecoder { int expire = packet.readInt(); long minimum = packet.readUnsignedInt(); return new StartOfAuthorityRecord(mName, rName, serial, refresh, retry, expire, minimum); + } else { + return null; + } }; - public static final Function> TXT = record -> { + static Function> TXT = record -> { + if (record.type() == DnsRecordType.TXT) { List list = new ArrayList<>(); ByteBuf data = ((DnsRawRecord)record).content(); int index = data.readerIndex(); while (index < data.writerIndex()) { - int len = data.getUnsignedByte(index++); - list.add(data.toString(index, len, CharsetUtil.UTF_8)); - index += len; + int len = data.getUnsignedByte(index++); + list.add(data.toString(index, len, CharsetUtil.UTF_8)); + index += len; } return list; + } else { + return null; + } }; - static Function address(int octets) { + static Function address(DnsRecordType type, int octets) { return record -> { + if (record.type() == type) { ByteBuf data = ((DnsRawRecord)record).content(); int size = data.readableBytes(); if (size != octets) { - throw new DecoderException("Invalid content length, or reader index when decoding address [index: " - + data.readerIndex() + ", expected length: " + octets + ", actual: " + size + "]."); + throw new DecoderException("Invalid content length, or reader index when decoding address [index: " + + data.readerIndex() + ", expected length: " + octets + ", actual: " + size + "]."); } byte[] address = new byte[octets]; data.getBytes(data.readerIndex(), address); try { - return InetAddress.getByAddress(address).getHostAddress(); + return InetAddress.getByAddress(address).getHostAddress(); } catch (UnknownHostException e) { - throw new DecoderException("Could not convert address " - + data.toString(data.readerIndex(), size, CharsetUtil.UTF_8) + " to InetAddress."); + throw new DecoderException("Could not convert address " + + data.toString(data.readerIndex(), size, CharsetUtil.UTF_8) + " to InetAddress."); } + } else { + return null; + } }; } @@ -216,11 +238,11 @@ static String getName(ByteBuf buf, int offset) { * @return the decoded resource record */ @SuppressWarnings("unchecked") - public static T decode(DnsRecord record) { + static T decode(DnsRecord record) { DnsRecordType type = record.type(); Function decoder = decoders.get(type); if (decoder == null) { - throw new IllegalStateException("Unsupported resource record type [id: " + type + "]."); + throw new DecoderException("DNS record decoding error occurred: Unsupported resource record type [id: " + type + "]."); } T result = null; try { diff --git a/src/main/java/io/vertx/core/dns/impl/SrvRecordImpl.java b/src/main/java/io/vertx/core/dns/impl/SrvRecordImpl.java index b98ddbfcc07..e5c20cc37b4 100644 --- a/src/main/java/io/vertx/core/dns/impl/SrvRecordImpl.java +++ b/src/main/java/io/vertx/core/dns/impl/SrvRecordImpl.java @@ -16,8 +16,9 @@ /** * @author Norman Maurer */ -public final class SrvRecordImpl implements SrvRecord, Comparable{ +final class SrvRecordImpl implements SrvRecord, Comparable{ + private final long ttl; private final int priority; private final int weight; private final int port; @@ -26,7 +27,8 @@ public final class SrvRecordImpl implements SrvRecord, Comparable{ private final String service; private final String target; - public SrvRecordImpl(int priority, int weight, int port, String name, String protocol, String service, String target) { + SrvRecordImpl(long ttl, int priority, int weight, int port, String name, String protocol, String service, String target) { + this.ttl = ttl; this.priority = priority; this.weight = weight; this.port = port; @@ -36,6 +38,11 @@ public SrvRecordImpl(int priority, int weight, int port, String name, String pro this.target = target; } + @Override + public long ttl() { + return ttl; + } + @Override public int priority() { return priority; diff --git a/src/main/java/io/vertx/core/dns/impl/decoder/StartOfAuthorityRecord.java b/src/main/java/io/vertx/core/dns/impl/StartOfAuthorityRecord.java similarity index 98% rename from src/main/java/io/vertx/core/dns/impl/decoder/StartOfAuthorityRecord.java rename to src/main/java/io/vertx/core/dns/impl/StartOfAuthorityRecord.java index 5b8c2c367e5..4160104757d 100644 --- a/src/main/java/io/vertx/core/dns/impl/decoder/StartOfAuthorityRecord.java +++ b/src/main/java/io/vertx/core/dns/impl/StartOfAuthorityRecord.java @@ -9,13 +9,13 @@ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ -package io.vertx.core.dns.impl.decoder; +package io.vertx.core.dns.impl; /** * Represents an SOA (start of authority) record, which defines global * parameters for a zone (domain). There can only be one SOA record per zone. */ -public class StartOfAuthorityRecord { +class StartOfAuthorityRecord { private final String primaryNameServer; private final String responsiblePerson; diff --git a/src/main/java/io/vertx/core/dns/impl/decoder/package-info.java b/src/main/java/io/vertx/core/dns/impl/decoder/package-info.java deleted file mode 100644 index 3d01694fbc6..00000000000 --- a/src/main/java/io/vertx/core/dns/impl/decoder/package-info.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -/** - * Handles the decoding of DNS response packets. - */ -package io.vertx.core.dns.impl.decoder; diff --git a/src/main/java/io/vertx/core/eventbus/EventBusOptions.java b/src/main/java/io/vertx/core/eventbus/EventBusOptions.java index 4d9ef22e83b..16d6c6a9a1e 100644 --- a/src/main/java/io/vertx/core/eventbus/EventBusOptions.java +++ b/src/main/java/io/vertx/core/eventbus/EventBusOptions.java @@ -13,6 +13,7 @@ import io.vertx.codegen.annotations.DataObject; import io.vertx.codegen.annotations.GenIgnore; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.ClientAuth; import io.vertx.core.json.JsonObject; @@ -26,7 +27,8 @@ * * @author Clement Escoffier */ -@DataObject(generateConverter = true, inheritConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public class EventBusOptions extends TCPSSLOptions { /** @@ -160,7 +162,7 @@ public EventBusOptions(EventBusOptions other) { * @param json the json object */ public EventBusOptions(JsonObject json) { - this(); + super(json); EventBusOptionsConverter.fromJson(json, this); } @@ -171,7 +173,7 @@ public EventBusOptions(JsonObject json) { * @return the JSON representation */ public JsonObject toJson() { - JsonObject json = new JsonObject(); + JsonObject json = super.toJson(); EventBusOptionsConverter.toJson(this, json); final String clusterPublicPortName = "clusterPublicPort"; if (json.containsKey(clusterPublicPortName) && json.getInteger(clusterPublicPortName) == DEFAULT_CLUSTER_PUBLIC_PORT) { @@ -366,36 +368,6 @@ public EventBusOptions setKeyCertOptions(KeyCertOptions options) { return this; } - @Override - public EventBusOptions setKeyStoreOptions(JksOptions options) { - super.setKeyStoreOptions(options); - return this; - } - - @Override - public EventBusOptions setPemKeyCertOptions(PemKeyCertOptions options) { - super.setPemKeyCertOptions(options); - return this; - } - - @Override - public EventBusOptions setPemTrustOptions(PemTrustOptions options) { - super.setPemTrustOptions(options); - return this; - } - - @Override - public EventBusOptions setPfxKeyCertOptions(PfxOptions options) { - super.setPfxKeyCertOptions(options); - return this; - } - - @Override - public EventBusOptions setPfxTrustOptions(PfxOptions options) { - super.setPfxTrustOptions(options); - return this; - } - @Override public EventBusOptions setSoLinger(int soLinger) { super.setSoLinger(soLinger); @@ -426,12 +398,6 @@ public EventBusOptions setTrustOptions(TrustOptions options) { return this; } - @Override - public EventBusOptions setTrustStoreOptions(JksOptions options) { - super.setTrustStoreOptions(options); - return this; - } - @Override public EventBusOptions setReceiveBufferSize(int receiveBufferSize) { super.setReceiveBufferSize(receiveBufferSize); @@ -472,16 +438,6 @@ public EventBusOptions setSslEngineOptions(SSLEngineOptions sslEngineOptions) { return (EventBusOptions) super.setSslEngineOptions(sslEngineOptions); } - @Override - public EventBusOptions setJdkSslEngineOptions(JdkSSLEngineOptions sslEngineOptions) { - return (EventBusOptions) super.setJdkSslEngineOptions(sslEngineOptions); - } - - @Override - public EventBusOptions setOpenSslEngineOptions(OpenSSLEngineOptions sslEngineOptions) { - return (EventBusOptions) super.setOpenSslEngineOptions(sslEngineOptions); - } - @Override public EventBusOptions setEnabledSecureTransportProtocols(Set enabledSecureTransportProtocols) { return (EventBusOptions) super.setEnabledSecureTransportProtocols(enabledSecureTransportProtocols); diff --git a/src/main/java/io/vertx/core/eventbus/MessageProducer.java b/src/main/java/io/vertx/core/eventbus/MessageProducer.java index 56eb097ee21..812b3c16b55 100644 --- a/src/main/java/io/vertx/core/eventbus/MessageProducer.java +++ b/src/main/java/io/vertx/core/eventbus/MessageProducer.java @@ -13,11 +13,7 @@ import io.vertx.codegen.annotations.Fluent; import io.vertx.codegen.annotations.VertxGen; -import io.vertx.core.AsyncResult; import io.vertx.core.Future; -import io.vertx.core.Handler; -import io.vertx.core.http.RequestOptions; -import io.vertx.core.streams.WriteStream; /** * Represents a stream of message that can be written to. @@ -45,9 +41,16 @@ public interface MessageProducer { /** * Write a message to the event-bus, either sending or publishing. * + * The returned {@link Future} completion depends on the producer type: + * + *

    + *
  • send or request: the future is completed successfully if the message has been written; otherwise, the future is failed
  • + *
  • publish: the future is failed if there is no recipient; otherwise, the future is completed successfully
  • + *
+ * + * In any case, a successfully completed {@link Future} is not a delivery guarantee. + * * @param body the message body - * @return a future called when the message has been successfully or failed to be written, this is not a delivery - * guarantee */ Future write(T body); diff --git a/src/main/java/io/vertx/core/eventbus/ReplyException.java b/src/main/java/io/vertx/core/eventbus/ReplyException.java index d9cc8b6c7c9..8e70afb3c27 100644 --- a/src/main/java/io/vertx/core/eventbus/ReplyException.java +++ b/src/main/java/io/vertx/core/eventbus/ReplyException.java @@ -26,6 +26,34 @@ public class ReplyException extends VertxException { private final ReplyFailure failureType; private final int failureCode; + /** + * Create a ReplyException with all attributes. + * + * @param failureType the failure type + * @param failureCode the failure code (e.g. 404) + * @param message the failure message + */ + public ReplyException(ReplyFailure failureType, int failureCode, String message, boolean noStackTrace) { + super(message, noStackTrace); + this.failureType = failureType; + this.failureCode = failureCode; + } + + /** + * Create a ReplyException with all attributes, including cause.
+ * Default {@link io.vertx.core.eventbus.impl.codecs.ReplyExceptionMessageCodec ReplyExceptionMessageCodec} doesn't support Cause!
+ * This ctor is meant to be used for extension, together with a custom codec. + * + * @param failureType the failure type + * @param failureCode the failure code (e.g. 404) + * @param message the failure message + */ + protected ReplyException(ReplyFailure failureType, int failureCode, String message, Throwable cause, boolean noStackTrace) { + super(message, cause, noStackTrace); + this.failureType = failureType; + this.failureCode = failureCode; + } + /** * Create a ReplyException * @@ -34,9 +62,7 @@ public class ReplyException extends VertxException { * @param message the failure message */ public ReplyException(ReplyFailure failureType, int failureCode, String message) { - super(message); - this.failureType = failureType; - this.failureCode = failureCode; + this(failureType, failureCode, message, true); } /** @@ -81,5 +107,4 @@ public String toString() { String message = getMessage(); return "(" + failureType + "," + failureCode + ") " + (message != null ? message : ""); } - } diff --git a/src/main/java/io/vertx/core/eventbus/impl/DeliveryContextBase.java b/src/main/java/io/vertx/core/eventbus/impl/DeliveryContextBase.java index fcbe09d6617..9d4338b8d8a 100644 --- a/src/main/java/io/vertx/core/eventbus/impl/DeliveryContextBase.java +++ b/src/main/java/io/vertx/core/eventbus/impl/DeliveryContextBase.java @@ -61,7 +61,15 @@ public void next() { Handler interceptor = interceptors[interceptorIdx]; invoking = true; interceptorIdx++; - context.emit(this, interceptor); + if (context.inThread()) { + context.dispatch(this, interceptor); + } else { + try { + interceptor.handle(this); + } catch (Throwable t) { + context.reportException(t); + } + } invoking = false; if (!invokeNext) { return; diff --git a/src/main/java/io/vertx/core/eventbus/impl/EventBusImpl.java b/src/main/java/io/vertx/core/eventbus/impl/EventBusImpl.java index 9b95ab0b4c2..625cc92a58c 100644 --- a/src/main/java/io/vertx/core/eventbus/impl/EventBusImpl.java +++ b/src/main/java/io/vertx/core/eventbus/impl/EventBusImpl.java @@ -11,7 +11,10 @@ package io.vertx.core.eventbus.impl; -import io.vertx.core.*; +import io.vertx.core.Future; +import io.vertx.core.Handler; +import io.vertx.core.MultiMap; +import io.vertx.core.Promise; import io.vertx.core.eventbus.*; import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.VertxInternal; @@ -28,6 +31,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.function.Consumer; import java.util.function.Function; /** @@ -115,16 +119,16 @@ public EventBus send(String address, Object message) { @Override public EventBus send(String address, Object message, DeliveryOptions options) { - MessageImpl msg = createMessage(true, address, options.getHeaders(), message, options.getCodecName()); - sendOrPubInternal(msg, options, null, null); + MessageImpl msg = createMessage(true, isLocalOnly(options), address, options.getHeaders(), message, options.getCodecName()); + sendOrPubInternal(msg, options, null); return this; } @Override public Future> request(String address, Object message, DeliveryOptions options) { - MessageImpl msg = createMessage(true, address, options.getHeaders(), message, options.getCodecName()); + MessageImpl msg = createMessage(true, isLocalOnly(options), address, options.getHeaders(), message, options.getCodecName()); ReplyHandler handler = createReplyHandler(msg, true, options); - sendOrPubInternal(msg, options, handler, null); + sendOrPubInternal(msg, options, handler); return handler.result(); } @@ -161,7 +165,7 @@ public EventBus publish(String address, Object message) { @Override public EventBus publish(String address, Object message, DeliveryOptions options) { - sendOrPubInternal(createMessage(false, address, options.getHeaders(), message, options.getCodecName()), options, null, null); + sendOrPubInternal(createMessage(false, isLocalOnly(options), address, options.getHeaders(), message, options.getCodecName()), options, null); return this; } @@ -169,7 +173,7 @@ public EventBus publish(String address, Object message, DeliveryOptions options) public MessageConsumer consumer(String address) { checkStarted(); Objects.requireNonNull(address, "address"); - return new MessageConsumerImpl<>(vertx, vertx.getOrCreateContext(), this, address, false); + return new MessageConsumerImpl<>(vertx.getOrCreateContext(), this, address, false); } @Override @@ -184,7 +188,7 @@ public MessageConsumer consumer(String address, Handler> handl public MessageConsumer localConsumer(String address) { checkStarted(); Objects.requireNonNull(address, "address"); - return new MessageConsumerImpl<>(vertx, vertx.getOrCreateContext(), this, address, true); + return new MessageConsumerImpl<>(vertx.getOrCreateContext(), this, address, true); } @Override @@ -249,18 +253,26 @@ public EventBusMetrics getMetrics() { return metrics; } - public MessageImpl createMessage(boolean send, String address, MultiMap headers, Object body, String codecName) { + public MessageImpl createMessage(boolean send, boolean localOnly, String address, MultiMap headers, Object body, String codecName) { Objects.requireNonNull(address, "no null address accepted"); - MessageCodec codec = codecManager.lookupCodec(body, codecName, true); + MessageCodec codec = codecManager.lookupCodec(body, codecName, localOnly); @SuppressWarnings("unchecked") MessageImpl msg = new MessageImpl(address, headers, body, codec, send, this); return msg; } - protected HandlerHolder addRegistration(String address, HandlerRegistration registration, boolean replyHandler, boolean localOnly, Promise promise) { - HandlerHolder holder = addLocalRegistration(address, registration, replyHandler, localOnly); - onLocalRegistration(holder, promise); - return holder; + protected Consumer> addRegistration(String address, HandlerRegistration registration, boolean broadcast, boolean localOnly, Promise promise) { + HandlerHolder holder = addLocalRegistration(address, registration, localOnly); + if (broadcast) { + onLocalRegistration(holder, promise); + } else { + if (promise != null) { + promise.complete(); + } + } + return p -> { + removeRegistration(holder, broadcast, p); + }; } protected void onLocalRegistration(HandlerHolder handlerHolder, Promise promise) { @@ -270,12 +282,12 @@ protected void onLocalRegistration(HandlerHolder handlerHolder, Promise HandlerHolder addLocalRegistration(String address, HandlerRegistration registration, - boolean replyHandler, boolean localOnly) { + boolean localOnly) { Objects.requireNonNull(address, "address"); ContextInternal context = registration.context; - HandlerHolder holder = createHandlerHolder(registration, replyHandler, localOnly, context); + HandlerHolder holder = createHandlerHolder(registration, localOnly, context); ConcurrentCyclicSequence handlers = new ConcurrentCyclicSequence().add(holder); ConcurrentCyclicSequence actualHandlers = handlerMap.merge( @@ -290,13 +302,17 @@ private HandlerHolder addLocalRegistration(String address, HandlerRegistr return holder; } - protected HandlerHolder createHandlerHolder(HandlerRegistration registration, boolean replyHandler, boolean localOnly, ContextInternal context) { - return new HandlerHolder<>(registration, replyHandler, localOnly, context); + protected HandlerHolder createHandlerHolder(HandlerRegistration registration, boolean localOnly, ContextInternal context) { + return new HandlerHolder<>(registration, localOnly, context); } - protected void removeRegistration(HandlerHolder handlerHolder, Promise promise) { + protected void removeRegistration(HandlerHolder handlerHolder, boolean broadcast, Promise promise) { removeLocalRegistration(handlerHolder); - onLocalUnregistration(handlerHolder, promise); + if (broadcast) { + onLocalUnregistration(handlerHolder, promise); + } else { + promise.complete(); + } } protected void onLocalUnregistration(HandlerHolder handlerHolder, Promise promise) { @@ -321,20 +337,24 @@ protected void sendReply(MessageImpl replyMessage, DeliveryOptions options, if (replyMessage.address() == null) { throw new IllegalStateException("address not specified"); } else { - sendOrPubInternal(new OutboundDeliveryContext<>(vertx.getOrCreateContext(), replyMessage, options, replyHandler, null)); + sendOrPubInternal(new OutboundDeliveryContext<>(vertx.getOrCreateContext(), replyMessage, options, replyHandler)); } } + protected void sendOrPub(ContextInternal ctx, MessageImpl message, DeliveryOptions options, Promise writePromise) { + sendLocally(message, writePromise); + } + protected void sendOrPub(OutboundDeliveryContext sendContext) { - sendLocally(sendContext); + sendOrPub(sendContext.ctx, sendContext.message, sendContext.options, sendContext); } - private void sendLocally(OutboundDeliveryContext sendContext) { - ReplyException failure = deliverMessageLocally(sendContext.message); + protected void sendLocally(MessageImpl message, Promise writePromise) { + ReplyException failure = deliverMessageLocally(message); if (failure != null) { - sendContext.written(failure); + writePromise.tryFail(failure); } else { - sendContext.written(null); + writePromise.tryComplete(); } } @@ -362,7 +382,7 @@ protected ReplyException deliverMessageLocally(MessageImpl msg) { if (metrics != null) { metrics.messageReceived(msg.address(), !msg.isSend(), messageLocal, handlers.size()); } - for (HandlerHolder holder: handlers) { + for (HandlerHolder holder : handlers) { if (messageLocal || !holder.isLocalOnly()) { holder.handler.receive(msg.copyBeforeReceive()); } @@ -392,8 +412,8 @@ protected String generateReplyAddress() { } ReplyHandler createReplyHandler(MessageImpl message, - boolean src, - DeliveryOptions options) { + boolean src, + DeliveryOptions options) { long timeout = options.getSendTimeout(); String replyAddress = generateReplyAddress(); message.setReplyAddress(replyAddress); @@ -403,8 +423,8 @@ ReplyHandler createReplyHandler(MessageImpl message, } public OutboundDeliveryContext newSendContext(MessageImpl message, DeliveryOptions options, - ReplyHandler handler, Promise writePromise) { - return new OutboundDeliveryContext<>(vertx.getOrCreateContext(), message, options, handler, writePromise); + ReplyHandler handler) { + return new OutboundDeliveryContext<>(vertx.getOrCreateContext(), message, options, handler); } public void sendOrPubInternal(OutboundDeliveryContext senderCtx) { @@ -414,10 +434,22 @@ public void sendOrPubInternal(OutboundDeliveryContext senderCtx) { senderCtx.next(); } - public void sendOrPubInternal(MessageImpl message, DeliveryOptions options, - ReplyHandler handler, Promise writePromise) { + public Future sendOrPubInternal(MessageImpl message, DeliveryOptions options, + ReplyHandler handler) { checkStarted(); - sendOrPubInternal(newSendContext(message, options, handler, writePromise)); + OutboundDeliveryContext ctx = newSendContext(message, options, handler); + sendOrPubInternal(ctx); + Future future = ctx.writePromise.future(); + if (message.send) { + return future; + } + return future.recover(throwable -> { + // For publish, we only care if there are no handlers + if (throwable instanceof ReplyException) { + return Future.failedFuture(throwable); + } + return Future.succeededFuture(); + }); } private Future unregisterAll() { @@ -446,7 +478,7 @@ private void removeInterceptor(AtomicReferenceFieldUpdater { public final ContextInternal context; public final HandlerRegistration handler; - public final boolean replyHandler; public final boolean localOnly; private boolean removed; - public HandlerHolder(HandlerRegistration handler, boolean replyHandler, boolean localOnly, ContextInternal context) { + public HandlerHolder(HandlerRegistration handler, boolean localOnly, ContextInternal context) { this.context = context; this.handler = handler; - this.replyHandler = replyHandler; this.localOnly = localOnly; } @@ -76,10 +74,6 @@ public HandlerRegistration getHandler() { return handler; } - public boolean isReplyHandler() { - return replyHandler; - } - public boolean isLocalOnly() { return localOnly; } diff --git a/src/main/java/io/vertx/core/eventbus/impl/HandlerRegistration.java b/src/main/java/io/vertx/core/eventbus/impl/HandlerRegistration.java index a1c15ea6c6d..096bbb98032 100644 --- a/src/main/java/io/vertx/core/eventbus/impl/HandlerRegistration.java +++ b/src/main/java/io/vertx/core/eventbus/impl/HandlerRegistration.java @@ -20,13 +20,15 @@ import io.vertx.core.spi.tracing.VertxTracer; import io.vertx.core.tracing.TracingPolicy; +import java.util.function.Consumer; + public abstract class HandlerRegistration implements Closeable { - public final ContextInternal context; - public final EventBusImpl bus; - public final String address; - public final boolean src; - private HandlerHolder registered; + protected final ContextInternal context; + protected final EventBusImpl bus; + protected final String address; + protected final boolean src; + private Consumer> registered; private Object metric; HandlerRegistration(ContextInternal context, @@ -52,17 +54,21 @@ void receive(MessageImpl msg) { }); } + public String address() { + return address; + } + protected abstract boolean doReceive(Message msg); protected abstract void dispatch(Message msg, ContextInternal context, Handler> handler); - synchronized void register(String repliedAddress, boolean localOnly, Promise promise) { + synchronized void register(boolean broadcast, boolean localOnly, Promise promise) { if (registered != null) { throw new IllegalStateException(); } - registered = bus.addRegistration(address, this, repliedAddress != null, localOnly, promise); + registered = bus.addRegistration(address, this, broadcast, localOnly, promise); if (bus.metrics != null) { - metric = bus.metrics.handlerRegistered(address, repliedAddress); + metric = bus.metrics.handlerRegistered(address); } } @@ -74,7 +80,7 @@ public Future unregister() { Promise promise = context.promise(); synchronized (this) { if (registered != null) { - bus.removeRegistration(registered, promise); + registered.accept(promise); registered = null; if (bus.metrics != null) { bus.metrics.handlerUnregistered(metric); diff --git a/src/main/java/io/vertx/core/eventbus/impl/MessageConsumerImpl.java b/src/main/java/io/vertx/core/eventbus/impl/MessageConsumerImpl.java index 776cbe0800d..a7b2658df36 100644 --- a/src/main/java/io/vertx/core/eventbus/impl/MessageConsumerImpl.java +++ b/src/main/java/io/vertx/core/eventbus/impl/MessageConsumerImpl.java @@ -35,10 +35,6 @@ public class MessageConsumerImpl extends HandlerRegistration implements Me private static final int DEFAULT_MAX_BUFFERED_MESSAGES = 1000; - private final Vertx vertx; - private final ContextInternal context; - private final EventBusImpl eventBus; - private final String address; private final boolean localOnly; private Handler> handler; private Handler endHandler; @@ -49,12 +45,8 @@ public class MessageConsumerImpl extends HandlerRegistration implements Me private Promise result; private boolean registered; - MessageConsumerImpl(Vertx vertx, ContextInternal context, EventBusImpl eventBus, String address, boolean localOnly) { + MessageConsumerImpl(ContextInternal context, EventBusImpl eventBus, String address, boolean localOnly) { super(context, eventBus, address, false); - this.vertx = vertx; - this.context = context; - this.eventBus = eventBus; - this.address = address; this.localOnly = localOnly; this.result = context.promise(); } @@ -93,11 +85,6 @@ public synchronized int getMaxBufferedMessages() { return maxBufferedMessages; } - @Override - public String address() { - return address; - } - @Override public synchronized Future completion() { return result.future(); @@ -112,7 +99,7 @@ public synchronized Future unregister() { if (pending.size() > 0) { Queue> discarded = pending; Handler> handler = discardHandler; - pending = new ArrayDeque<>(); + pending = new ArrayDeque<>(8); for (Message msg : discarded) { discard(msg); if (handler != null) { @@ -216,7 +203,7 @@ public synchronized MessageConsumer handler(Handler> h) { registered = true; Promise p = result; Promise registration = context.promise(); - register(null, localOnly, registration); + register(true, localOnly, registration); registration.future().onComplete(ar -> { if (ar.succeeded()) { p.tryComplete(); @@ -267,7 +254,7 @@ public synchronized MessageConsumer fetch(long amount) { public synchronized MessageConsumer endHandler(Handler endHandler) { if (endHandler != null) { // We should use the HandlerHolder context to properly do this (needs small refactoring) - Context endCtx = vertx.getOrCreateContext(); + Context endCtx = context.owner().getOrCreateContext(); this.endHandler = v1 -> endCtx.runOnContext(v2 -> endHandler.handle(null)); } else { this.endHandler = null; diff --git a/src/main/java/io/vertx/core/eventbus/impl/MessageImpl.java b/src/main/java/io/vertx/core/eventbus/impl/MessageImpl.java index c2919156d20..c97ea8a5e89 100644 --- a/src/main/java/io/vertx/core/eventbus/impl/MessageImpl.java +++ b/src/main/java/io/vertx/core/eventbus/impl/MessageImpl.java @@ -119,7 +119,7 @@ public Future> replyAndRequest(Object message, DeliveryOptions op } protected MessageImpl createReply(Object message, DeliveryOptions options) { - MessageImpl reply = bus.createMessage(true, replyAddress, options.getHeaders(), message, options.getCodecName()); + MessageImpl reply = bus.createMessage(true, isLocal(), replyAddress, options.getHeaders(), message, options.getCodecName()); reply.trace = trace; return reply; } diff --git a/src/main/java/io/vertx/core/eventbus/impl/MessageProducerImpl.java b/src/main/java/io/vertx/core/eventbus/impl/MessageProducerImpl.java index 41d4bb2be81..3f227b409e0 100644 --- a/src/main/java/io/vertx/core/eventbus/impl/MessageProducerImpl.java +++ b/src/main/java/io/vertx/core/eventbus/impl/MessageProducerImpl.java @@ -12,11 +12,9 @@ package io.vertx.core.eventbus.impl; import io.vertx.core.Future; -import io.vertx.core.Promise; import io.vertx.core.Vertx; import io.vertx.core.eventbus.*; import io.vertx.core.impl.ContextInternal; -import io.vertx.core.impl.VertxInternal; /** * @author Julien Viet @@ -27,6 +25,7 @@ public class MessageProducerImpl implements MessageProducer { private final EventBusImpl bus; private final boolean send; private final String address; + private final boolean localOnly; private DeliveryOptions options; public MessageProducerImpl(Vertx vertx, String address, boolean send, DeliveryOptions options) { @@ -35,6 +34,7 @@ public MessageProducerImpl(Vertx vertx, String address, boolean send, DeliveryOp this.address = address; this.send = send; this.options = options; + this.localOnly = vertx.isClustered() ? options.isLocalOnly() : true; } @Override @@ -45,14 +45,8 @@ public synchronized MessageProducer deliveryOptions(DeliveryOptions options) @Override public Future write(T body) { - Promise promise = ((VertxInternal)vertx).getOrCreateContext().promise(); - write(body, promise); - return promise.future(); - } - - private void write(T data, Promise handler) { - MessageImpl msg = bus.createMessage(send, address, options.getHeaders(), data, options.getCodecName()); - bus.sendOrPubInternal(msg, options, null, handler); + MessageImpl msg = bus.createMessage(send, localOnly, address, options.getHeaders(), body, options.getCodecName()); + return bus.sendOrPubInternal(msg, options, null); } @Override diff --git a/src/main/java/io/vertx/core/eventbus/impl/MessageTagExtractor.java b/src/main/java/io/vertx/core/eventbus/impl/MessageTagExtractor.java index d762b6ff990..a11a7e72312 100644 --- a/src/main/java/io/vertx/core/eventbus/impl/MessageTagExtractor.java +++ b/src/main/java/io/vertx/core/eventbus/impl/MessageTagExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -27,21 +27,31 @@ private MessageTagExtractor() { @Override public int len(Message obj) { - return 1; + return 3; } @Override public String name(Message obj, int index) { - if (index == 0) { - return "message_bus.destination"; + switch (index) { + case 0: + return "message_bus.destination"; + case 1: + return "message_bus.system"; + case 2: + return "message_bus.operation"; } throw new IndexOutOfBoundsException("Invalid tag index " + index); } @Override public String value(Message obj, int index) { - if (index == 0) { - return obj.address(); + switch (index) { + case 0: + return obj.address(); + case 1: + return "vertx-eventbus"; + case 2: + return "publish"; } throw new IndexOutOfBoundsException("Invalid tag index " + index); } diff --git a/src/main/java/io/vertx/core/eventbus/impl/OutboundDeliveryContext.java b/src/main/java/io/vertx/core/eventbus/impl/OutboundDeliveryContext.java index 657ae791543..495eeda5cbd 100644 --- a/src/main/java/io/vertx/core/eventbus/impl/OutboundDeliveryContext.java +++ b/src/main/java/io/vertx/core/eventbus/impl/OutboundDeliveryContext.java @@ -10,8 +10,7 @@ */ package io.vertx.core.eventbus.impl; -import io.vertx.core.AsyncResult; -import io.vertx.core.Handler; +import io.vertx.core.Future; import io.vertx.core.Promise; import io.vertx.core.eventbus.DeliveryOptions; import io.vertx.core.eventbus.ReplyException; @@ -25,31 +24,43 @@ import java.util.function.BiConsumer; -public class OutboundDeliveryContext extends DeliveryContextBase implements Handler> { +public class OutboundDeliveryContext extends DeliveryContextBase implements Promise { public final ContextInternal ctx; public final DeliveryOptions options; public final ReplyHandler replyHandler; - private final Promise writePromise; + public final Promise writePromise; private boolean src; EventBusImpl bus; EventBusMetrics metrics; - OutboundDeliveryContext(ContextInternal ctx, MessageImpl message, DeliveryOptions options, ReplyHandler replyHandler, Promise writePromise) { + OutboundDeliveryContext(ContextInternal ctx, MessageImpl message, DeliveryOptions options, ReplyHandler replyHandler) { super(message, message.bus.outboundInterceptors(), ctx); this.ctx = ctx; this.options = options; this.replyHandler = replyHandler; - this.writePromise = writePromise; + this.writePromise = ctx.promise(); } @Override - public void handle(AsyncResult event) { - written(event.cause()); + public boolean tryComplete(Void result) { + written(null); + return true; } - public void written(Throwable failure) { + @Override + public boolean tryFail(Throwable cause) { + written(cause); + return false; + } + + @Override + public Future future() { + throw new UnsupportedOperationException(); + } + + private void written(Throwable failure) { // Metrics if (metrics != null) { @@ -82,9 +93,9 @@ public void written(Throwable failure) { // Notify promise finally if (writePromise != null) { if (failure == null) { - writePromise.complete(); + writePromise.tryComplete(); } else { - writePromise.fail(failure); + writePromise.tryFail(failure); } } } diff --git a/src/main/java/io/vertx/core/eventbus/impl/ReplyHandler.java b/src/main/java/io/vertx/core/eventbus/impl/ReplyHandler.java index 649668090e1..a3d112edadf 100644 --- a/src/main/java/io/vertx/core/eventbus/impl/ReplyHandler.java +++ b/src/main/java/io/vertx/core/eventbus/impl/ReplyHandler.java @@ -23,23 +23,17 @@ class ReplyHandler extends HandlerRegistration implements Handler { - private final EventBusImpl eventBus; - private final ContextInternal context; private final Promise> result; private final long timeoutID; private final long timeout; - private final boolean src; private final String repliedAddress; Object trace; ReplyHandler(EventBusImpl eventBus, ContextInternal context, String address, String repliedAddress, boolean src, long timeout) { super(context, eventBus, address, src); - this.eventBus = eventBus; - this.context = context; this.result = context.promise(); - this.src = src; this.repliedAddress = repliedAddress; - this.timeoutID = eventBus.vertx.setTimer(timeout, this); + this.timeoutID = context.setTimer(timeout, this); this.timeout = timeout; } @@ -56,7 +50,7 @@ Future> result() { } void fail(ReplyException failure) { - if (eventBus.vertx.cancelTimer(timeoutID)) { + if (context.owner().cancelTimer(timeoutID)) { unregister(); doFail(failure); } @@ -65,8 +59,8 @@ void fail(ReplyException failure) { private void doFail(ReplyException failure) { trace(null, failure); result.fail(failure); - if (eventBus.metrics != null) { - eventBus.metrics.replyFailure(repliedAddress, failure.failureType()); + if (bus.metrics != null) { + bus.metrics.replyFailure(repliedAddress, failure.failureType()); } } @@ -83,12 +77,12 @@ protected boolean doReceive(Message reply) { } void register() { - register(repliedAddress, true, null); + register(false, false, null); } @Override protected void dispatch(Message reply, ContextInternal context, Handler> handler /* null */) { - if (eventBus.vertx.cancelTimer(timeoutID)) { + if (context.owner().cancelTimer(timeoutID)) { unregister(); if (reply.body() instanceof ReplyException) { doFail((ReplyException) reply.body()); diff --git a/src/main/java/io/vertx/core/eventbus/impl/clustered/ClusteredEventBus.java b/src/main/java/io/vertx/core/eventbus/impl/clustered/ClusteredEventBus.java index 3fe279ecc00..ab0faf2e5c3 100644 --- a/src/main/java/io/vertx/core/eventbus/impl/clustered/ClusteredEventBus.java +++ b/src/main/java/io/vertx/core/eventbus/impl/clustered/ClusteredEventBus.java @@ -11,12 +11,10 @@ package io.vertx.core.eventbus.impl.clustered; -import io.vertx.core.Handler; -import io.vertx.core.MultiMap; -import io.vertx.core.Promise; -import io.vertx.core.VertxOptions; +import io.vertx.core.*; import io.vertx.core.buffer.Buffer; import io.vertx.core.eventbus.AddressHelper; +import io.vertx.core.eventbus.DeliveryOptions; import io.vertx.core.eventbus.EventBusOptions; import io.vertx.core.eventbus.MessageCodec; import io.vertx.core.eventbus.impl.CodecManager; @@ -24,10 +22,8 @@ import io.vertx.core.eventbus.impl.HandlerHolder; import io.vertx.core.eventbus.impl.HandlerRegistration; import io.vertx.core.eventbus.impl.MessageImpl; -import io.vertx.core.eventbus.impl.OutboundDeliveryContext; import io.vertx.core.impl.CloseFuture; import io.vertx.core.impl.ContextInternal; -import io.vertx.core.impl.EventLoopContext; import io.vertx.core.impl.VertxInternal; import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; @@ -70,7 +66,7 @@ public class ClusteredEventBus extends EventBusImpl { private final NetClient client; private final ConcurrentMap connections = new ConcurrentHashMap<>(); - private final EventLoopContext ebContext; + private final ContextInternal ebContext; private NodeInfo nodeInfo; private String nodeId; @@ -149,9 +145,9 @@ public void close(Promise promise) { } @Override - public MessageImpl createMessage(boolean send, String address, MultiMap headers, Object body, String codecName) { + public MessageImpl createMessage(boolean send, boolean local, String address, MultiMap headers, Object body, String codecName) { Objects.requireNonNull(address, "no null address accepted"); - MessageCodec codec = codecManager.lookupCodec(body, codecName, false); + MessageCodec codec = codecManager.lookupCodec(body, codecName, local); @SuppressWarnings("unchecked") ClusteredMessage msg = new ClusteredMessage(nodeId, address, headers, body, codec, send, this); return msg; @@ -159,76 +155,68 @@ public MessageImpl createMessage(boolean send, String address, MultiMap headers, @Override protected void onLocalRegistration(HandlerHolder handlerHolder, Promise promise) { - if (!handlerHolder.isReplyHandler()) { - RegistrationInfo registrationInfo = new RegistrationInfo( - nodeId, - handlerHolder.getSeq(), - handlerHolder.isLocalOnly() - ); - clusterManager.addRegistration(handlerHolder.getHandler().address, registrationInfo, Objects.requireNonNull(promise)); - } else if (promise != null) { - promise.complete(); - } + RegistrationInfo registrationInfo = new RegistrationInfo( + nodeId, + handlerHolder.getSeq(), + handlerHolder.isLocalOnly() + ); + clusterManager.addRegistration(handlerHolder.getHandler().address(), registrationInfo, Objects.requireNonNull(promise)); } @Override - protected HandlerHolder createHandlerHolder(HandlerRegistration registration, boolean replyHandler, boolean localOnly, ContextInternal context) { - return new ClusteredHandlerHolder<>(registration, replyHandler, localOnly, context, handlerSequence.getAndIncrement()); + protected HandlerHolder createHandlerHolder(HandlerRegistration registration, boolean localOnly, ContextInternal context) { + return new ClusteredHandlerHolder<>(registration, localOnly, context, handlerSequence.getAndIncrement()); } @Override protected void onLocalUnregistration(HandlerHolder handlerHolder, Promise completionHandler) { - if (!handlerHolder.isReplyHandler()) { - RegistrationInfo registrationInfo = new RegistrationInfo( - nodeId, - handlerHolder.getSeq(), - handlerHolder.isLocalOnly() - ); - Promise promise = Promise.promise(); - clusterManager.removeRegistration(handlerHolder.getHandler().address, registrationInfo, promise); - promise.future().onComplete(completionHandler); - } else { - completionHandler.complete(); - } + RegistrationInfo registrationInfo = new RegistrationInfo( + nodeId, + handlerHolder.getSeq(), + handlerHolder.isLocalOnly() + ); + Promise promise = Promise.promise(); + clusterManager.removeRegistration(handlerHolder.getHandler().address(), registrationInfo, promise); + promise.future().onComplete(completionHandler); } @Override - protected void sendOrPub(OutboundDeliveryContext sendContext) { - if (((ClusteredMessage) sendContext.message).getRepliedTo() != null) { - clusteredSendReply(((ClusteredMessage) sendContext.message).getRepliedTo(), sendContext); - } else if (sendContext.options.isLocalOnly()) { - super.sendOrPub(sendContext); + protected void sendOrPub(ContextInternal ctx, MessageImpl message, DeliveryOptions options, Promise writePromise) { + if (((ClusteredMessage) message).getRepliedTo() != null) { + clusteredSendReply(message, writePromise, ((ClusteredMessage) message).getRepliedTo()); + } else if (options.isLocalOnly()) { + sendLocally(message, writePromise); } else { - Serializer serializer = Serializer.get(sendContext.ctx); - if (sendContext.message.isSend()) { - Promise promise = sendContext.ctx.promise(); - serializer.queue(sendContext.message, nodeSelector::selectForSend, promise); + Serializer serializer = Serializer.get(ctx); + if (message.isSend()) { + Promise promise = ctx.promise(); + serializer.queue(message, nodeSelector::selectForSend, promise); promise.future().onComplete(ar -> { if (ar.succeeded()) { - sendToNode(sendContext, ar.result()); + sendToNode(ar.result(), message, writePromise); } else { - sendOrPublishFailed(sendContext, ar.cause()); + sendOrPublishFailed(writePromise, ar.cause()); } }); } else { - Promise> promise = sendContext.ctx.promise(); - serializer.queue(sendContext.message, nodeSelector::selectForPublish, promise); + Promise> promise = ctx.promise(); + serializer.queue(message, nodeSelector::selectForPublish, promise); promise.future().onComplete(ar -> { if (ar.succeeded()) { - sendToNodes(sendContext, ar.result()); + sendToNodes(ar.result(), message, writePromise); } else { - sendOrPublishFailed(sendContext, ar.cause()); + sendOrPublishFailed(writePromise, ar.cause()); } }); } } } - private void sendOrPublishFailed(OutboundDeliveryContext sendContext, Throwable cause) { + private void sendOrPublishFailed(Promise promise, Throwable cause) { if (log.isDebugEnabled()) { log.error("Failed to send message", cause); } - sendContext.written(cause); + promise.tryFail(cause); } @Override @@ -252,7 +240,7 @@ protected HandlerHolder nextHandler(ConcurrentCyclicSequence hand Iterator iterator = handlers.iterator(false); while (iterator.hasNext()) { HandlerHolder next = iterator.next(); - if (next.isReplyHandler() || !next.isLocalOnly()) { + if (!next.isLocalOnly()) { handlerHolder = next; break; } @@ -329,39 +317,39 @@ public void handle(Buffer buff) { }; } - private void sendToNode(OutboundDeliveryContext sendContext, String nodeId) { + private void sendToNode(String nodeId, MessageImpl message, Promise writePromise) { if (nodeId != null && !nodeId.equals(this.nodeId)) { - sendRemote(sendContext, nodeId, sendContext.message); + sendRemote(nodeId, message, writePromise); } else { - super.sendOrPub(sendContext); + sendLocally(message, writePromise); } } - private void sendToNodes(OutboundDeliveryContext sendContext, Iterable nodeIds) { + private void sendToNodes(Iterable nodeIds, MessageImpl message, Promise writePromise) { boolean sentRemote = false; if (nodeIds != null) { for (String nid : nodeIds) { if (!sentRemote) { sentRemote = true; } - sendToNode(sendContext, nid); + // Write promise might be completed several times!!!! + sendToNode(nid, message, writePromise); } } if (!sentRemote) { - super.sendOrPub(sendContext); + sendLocally(message, writePromise); } } - private void clusteredSendReply(String replyDest, OutboundDeliveryContext sendContext) { - MessageImpl message = sendContext.message; + private void clusteredSendReply(MessageImpl message, Promise writePromise, String replyDest) { if (!replyDest.equals(nodeId)) { - sendRemote(sendContext, replyDest, message); + sendRemote(replyDest, message, writePromise); } else { - super.sendOrPub(sendContext); + sendLocally(message, writePromise); } } - private void sendRemote(OutboundDeliveryContext sendContext, String remoteNodeId, MessageImpl message) { + private void sendRemote(String remoteNodeId, MessageImpl message, Promise writePromise) { // We need to deal with the fact that connecting can take some time and is async, and we cannot // block to wait for it. So we add any sends to a pending list if not connected yet. // Once we connect we send them. @@ -380,7 +368,7 @@ private void sendRemote(OutboundDeliveryContext sendContext, String remoteNod holder.connect(); } } - holder.writeMessage(sendContext); + holder.writeMessage(message, writePromise); } ConcurrentMap connections() { diff --git a/src/main/java/io/vertx/core/eventbus/impl/clustered/ClusteredHandlerHolder.java b/src/main/java/io/vertx/core/eventbus/impl/clustered/ClusteredHandlerHolder.java index 8940eff5184..c5703d86117 100644 --- a/src/main/java/io/vertx/core/eventbus/impl/clustered/ClusteredHandlerHolder.java +++ b/src/main/java/io/vertx/core/eventbus/impl/clustered/ClusteredHandlerHolder.java @@ -19,8 +19,8 @@ public class ClusteredHandlerHolder extends HandlerHolder { private final long seq; - public ClusteredHandlerHolder(HandlerRegistration handler, boolean replyHandler, boolean localOnly, ContextInternal context, long seq) { - super(handler, replyHandler, localOnly, context); + public ClusteredHandlerHolder(HandlerRegistration handler, boolean localOnly, ContextInternal context, long seq) { + super(handler, localOnly, context); this.seq = seq; } diff --git a/src/main/java/io/vertx/core/eventbus/impl/clustered/ConnectionHolder.java b/src/main/java/io/vertx/core/eventbus/impl/clustered/ConnectionHolder.java index 07348e82440..342b0c2e0a3 100644 --- a/src/main/java/io/vertx/core/eventbus/impl/clustered/ConnectionHolder.java +++ b/src/main/java/io/vertx/core/eventbus/impl/clustered/ConnectionHolder.java @@ -14,7 +14,7 @@ import io.vertx.core.Promise; import io.vertx.core.buffer.Buffer; import io.vertx.core.eventbus.EventBusOptions; -import io.vertx.core.eventbus.impl.OutboundDeliveryContext; +import io.vertx.core.eventbus.impl.MessageImpl; import io.vertx.core.eventbus.impl.codecs.PingMessageCodec; import io.vertx.core.impl.VertxInternal; import io.vertx.core.impl.logging.Logger; @@ -41,7 +41,7 @@ class ConnectionHolder { private final VertxInternal vertx; private final EventBusMetrics metrics; - private Queue> pending; + private Queue pendingWrites; private NetSocket socket; private boolean connected; private long timeoutID = -1; @@ -70,21 +70,21 @@ void connect() { } // TODO optimise this (contention on monitor) - synchronized void writeMessage(OutboundDeliveryContext ctx) { + synchronized void writeMessage(MessageImpl message, Promise writePromise) { if (connected) { - Buffer data = ((ClusteredMessage) ctx.message).encodeToWire(); + Buffer data = ((ClusteredMessage) message).encodeToWire(); if (metrics != null) { - metrics.messageWritten(ctx.message.address(), data.length()); + metrics.messageWritten(message.address(), data.length()); } - socket.write(data).onComplete(ctx); + socket.write(data).onComplete(writePromise); } else { - if (pending == null) { + if (pendingWrites == null) { if (log.isDebugEnabled()) { log.debug("Not connected to server " + remoteNodeId + " - starting queuing"); } - pending = new ArrayDeque<>(); + pendingWrites = new ArrayDeque<>(); } - pending.add(ctx); + pendingWrites.add(new MessageWrite(message, writePromise)); } } @@ -100,10 +100,10 @@ private void close(Throwable cause) { vertx.cancelTimer(pingTimeoutID); } synchronized (this) { - OutboundDeliveryContext msg; - if (pending != null) { - while ((msg = pending.poll()) != null) { - msg.written(cause); + MessageWrite msg; + if (pendingWrites != null) { + while ((msg = pendingWrites.poll()) != null) { + msg.writePromise.tryFail(cause); } } } @@ -146,18 +146,27 @@ private synchronized void connected(NetSocket socket) { }); // Start a pinger schedulePing(); - if (pending != null) { + if (pendingWrites != null) { if (log.isDebugEnabled()) { log.debug("Draining the queue for server " + remoteNodeId); } - for (OutboundDeliveryContext ctx : pending) { + for (MessageWrite ctx : pendingWrites) { Buffer data = ((ClusteredMessage)ctx.message).encodeToWire(); if (metrics != null) { metrics.messageWritten(ctx.message.address(), data.length()); } - socket.write(data).onComplete(ctx); + socket.write(data).onComplete(ctx.writePromise); } } - pending = null; + pendingWrites = null; + } + + private static class MessageWrite { + final MessageImpl message; + final Promise writePromise; + MessageWrite(MessageImpl message, Promise writePromise) { + this.message = message; + this.writePromise = writePromise; + } } } diff --git a/src/main/java/io/vertx/core/eventbus/impl/clustered/Serializer.java b/src/main/java/io/vertx/core/eventbus/impl/clustered/Serializer.java index 145011725b9..08d0e403ae8 100644 --- a/src/main/java/io/vertx/core/eventbus/impl/clustered/Serializer.java +++ b/src/main/java/io/vertx/core/eventbus/impl/clustered/Serializer.java @@ -60,7 +60,7 @@ public static Serializer get(ContextInternal context) { return serializer; } - public void queue(Message message, BiConsumer, Promise> selectHandler, Promise promise) { + public void queue(Message message, BiConsumer> selectHandler, Promise promise) { ctx.emit(v -> { String address = message.address(); SerializerQueue queue = queues.computeIfAbsent(address, SerializerQueue::new); @@ -110,7 +110,7 @@ void checkPending() { } } - void add(Message msg, BiConsumer, Promise> selectHandler, Promise promise) { + void add(Message msg, BiConsumer> selectHandler, Promise promise) { SerializedTask serializedTask = new SerializedTask<>(ctx, msg, selectHandler); Future fut = serializedTask.internalPromise.future(); fut.onComplete(promise); @@ -136,20 +136,20 @@ void close() { private class SerializedTask implements Handler> { final Message msg; - final BiConsumer, Promise> selectHandler; + final BiConsumer> selectHandler; final Promise internalPromise; SerializedTask( ContextInternal context, Message msg, - BiConsumer, Promise> selectHandler) { + BiConsumer> selectHandler) { this.msg = msg; this.selectHandler = selectHandler; this.internalPromise = context.promise(); } void process() { - selectHandler.accept(msg, internalPromise); + selectHandler.accept(msg.address(), internalPromise); } @Override diff --git a/src/main/java/io/vertx/core/file/AsyncFile.java b/src/main/java/io/vertx/core/file/AsyncFile.java index 6d58ea62f2f..10ce96bc121 100644 --- a/src/main/java/io/vertx/core/file/AsyncFile.java +++ b/src/main/java/io/vertx/core/file/AsyncFile.java @@ -17,10 +17,14 @@ import io.vertx.codegen.annotations.VertxGen; import io.vertx.core.Future; import io.vertx.core.Handler; +import io.vertx.core.VertxException; import io.vertx.core.buffer.Buffer; import io.vertx.core.streams.ReadStream; import io.vertx.core.streams.WriteStream; +import java.util.function.Function; +import java.util.function.Supplier; + /** * Represents a file on the file-system which can be read from, or written to asynchronously. *

@@ -171,7 +175,9 @@ public interface AsyncFile extends ReadStream, WriteStream { * @return the lock if it can be acquired immediately, otherwise {@code null} */ @Unstable - @Nullable AsyncFileLock tryLock(); + default @Nullable AsyncFileLock tryLock() { + return tryLock(0, Long.MAX_VALUE, false); + } /** * Try to acquire a lock on a portion of this file. @@ -190,7 +196,9 @@ public interface AsyncFile extends ReadStream, WriteStream { * @return a future indicating the completion of this operation */ @Unstable - Future lock(); + default Future lock() { + return lock(0, Long.MAX_VALUE, false); + } /** * Acquire a lock on a portion of this file. @@ -203,4 +211,48 @@ public interface AsyncFile extends ReadStream, WriteStream { @Unstable Future lock(long position, long size, boolean shared); + /** + * Acquire a non-shared lock on the entire file. + * + *

When the {@code block} is called, the lock is already acquired, it will be released when the + * {@code Future} returned by the block completes. + * + *

When the {@code block} fails, the lock is released and the returned future is failed with the cause of the failure. + * + * @param block the code block called after lock acquisition + * @return the future returned by the {@code block} + */ + @Unstable + default Future withLock(Supplier> block) { + return withLock(0, Long.MAX_VALUE, false, block); + } + + /** + * Acquire a lock on a portion of this file. + * + *

When the {@code block} is called, the lock is already acquired , it will be released when the + * {@code Future} returned by the block completes. + * + *

When the {@code block} fails, the lock is released and the returned future is failed with the cause of the failure. + * + * @param position where the region starts + * @param size the size of the region + * @param shared whether the lock should be shared + * @param block the code block called after lock acquisition + * @return the future returned by the {@code block} + */ + @Unstable + default Future withLock(long position, long size, boolean shared, Supplier> block) { + return lock(position, size, shared) + .compose(lock -> { + Future res; + try { + res = block.get(); + } catch (Exception e) { + lock.release(); + throw new VertxException(e); + } + return res.eventually(() -> lock.release()); + }); + } } diff --git a/src/main/java/io/vertx/core/file/CopyOptions.java b/src/main/java/io/vertx/core/file/CopyOptions.java index e6034feac54..97efe77b37f 100644 --- a/src/main/java/io/vertx/core/file/CopyOptions.java +++ b/src/main/java/io/vertx/core/file/CopyOptions.java @@ -12,6 +12,7 @@ package io.vertx.core.file; import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.json.JsonObject; /** @@ -19,7 +20,8 @@ * * @author Thomas Segismont */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public class CopyOptions { /** diff --git a/src/main/java/io/vertx/core/file/FileSystemOptions.java b/src/main/java/io/vertx/core/file/FileSystemOptions.java index fb9a56dd2c0..8d0771c6197 100644 --- a/src/main/java/io/vertx/core/file/FileSystemOptions.java +++ b/src/main/java/io/vertx/core/file/FileSystemOptions.java @@ -13,6 +13,7 @@ package io.vertx.core.file; import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.file.impl.FileResolverImpl; import io.vertx.core.json.JsonObject; @@ -22,7 +23,8 @@ * Vert.x file system base configuration, this class can be extended by provider implementations to configure * those specific implementations. */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public class FileSystemOptions { /** diff --git a/src/main/java/io/vertx/core/file/FileSystemProps.java b/src/main/java/io/vertx/core/file/FileSystemProps.java index 1af0a468503..e18b54b2dcd 100644 --- a/src/main/java/io/vertx/core/file/FileSystemProps.java +++ b/src/main/java/io/vertx/core/file/FileSystemProps.java @@ -22,6 +22,11 @@ @VertxGen public interface FileSystemProps { + /** + * @return The name of this file store + */ + String name(); + /** * @return The total space on the file system, in bytes */ diff --git a/src/main/java/io/vertx/core/file/OpenOptions.java b/src/main/java/io/vertx/core/file/OpenOptions.java index 940c94fd52d..58efa4e8400 100644 --- a/src/main/java/io/vertx/core/file/OpenOptions.java +++ b/src/main/java/io/vertx/core/file/OpenOptions.java @@ -12,6 +12,7 @@ package io.vertx.core.file; import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.json.JsonObject; /** @@ -19,7 +20,8 @@ * * @author Tim Fox */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public class OpenOptions { public static final String DEFAULT_PERMS = null; diff --git a/src/main/java/io/vertx/core/file/impl/AsyncFileImpl.java b/src/main/java/io/vertx/core/file/impl/AsyncFileImpl.java index 169db42de73..6c21f1deeb7 100644 --- a/src/main/java/io/vertx/core/file/impl/AsyncFileImpl.java +++ b/src/main/java/io/vertx/core/file/impl/AsyncFileImpl.java @@ -17,6 +17,8 @@ import io.vertx.core.Handler; import io.vertx.core.Promise; import io.vertx.core.buffer.Buffer; +import io.vertx.core.buffer.impl.BufferImpl; +import io.vertx.core.buffer.impl.BufferInternal; import io.vertx.core.file.AsyncFile; import io.vertx.core.file.AsyncFileLock; import io.vertx.core.file.FileSystemException; @@ -203,7 +205,7 @@ private synchronized void doWrite(Buffer buffer, long position, Handler 1) { doWrite(buf.nioBuffers(), position, wrapped); } else { @@ -410,10 +412,10 @@ private void handleEnd() { private synchronized void doFlush(Handler> handler) { checkClosed(); - context.executeBlockingInternal((Promise fut) -> { + context.executeBlockingInternal(() -> { try { ch.force(false); - fut.complete(); + return null; } catch (IOException e) { throw new FileSystemException(e); } @@ -523,13 +525,9 @@ private void checkContext() { } private void doClose(Handler> handler) { - context.executeBlockingInternal(res -> { - try { - ch.close(); - res.complete(null); - } catch (IOException e) { - res.fail(e); - } + context.executeBlockingInternal(() -> { + ch.close(); + return null; }).onComplete(handler); } @@ -556,18 +554,7 @@ public long sizeBlocking() { @Override public Future size() { - return vertx.getOrCreateContext().executeBlockingInternal(prom -> { - prom.complete(sizeBlocking()); - }); - } - - @Override - public AsyncFileLock tryLock() { - try { - return new AsyncFileLockImpl(vertx, ch.tryLock()); - } catch (IOException e) { - throw new FileSystemException(e); - } + return vertx.getOrCreateContext().executeBlockingInternal(this::sizeBlocking); } @Override @@ -579,11 +566,6 @@ public AsyncFileLock tryLock(long position, long size, boolean shared) { } } - @Override - public Future lock() { - return lock(0, Long.MAX_VALUE, false); - } - private static CompletionHandler> LOCK_COMPLETION = new CompletionHandler>() { @Override public void completed(FileLock result, PromiseInternal p) { @@ -599,8 +581,9 @@ public void failed(Throwable t, PromiseInternal p) { @Override public Future lock(long position, long size, boolean shared) { PromiseInternal promise = vertx.promise(); - vertx.executeBlockingInternal(prom -> { + vertx.executeBlockingInternal(() -> { ch.lock(position, size, shared, promise, LOCK_COMPLETION); + return null; }).onComplete(ar -> { if (ar.failed()) { // Happens only if ch.lock throws a RuntimeException diff --git a/src/main/java/io/vertx/core/file/impl/AsyncFileLockImpl.java b/src/main/java/io/vertx/core/file/impl/AsyncFileLockImpl.java index 506a4274f5f..40bd42cbf88 100644 --- a/src/main/java/io/vertx/core/file/impl/AsyncFileLockImpl.java +++ b/src/main/java/io/vertx/core/file/impl/AsyncFileLockImpl.java @@ -59,9 +59,7 @@ public boolean isValidBlocking() { @Override public Future isValid() { - return vertx.getOrCreateContext().executeBlockingInternal(prom -> { - prom.complete(isValidBlocking()); - }); + return vertx.getOrCreateContext().executeBlockingInternal(this::isValidBlocking); } @Override @@ -75,12 +73,12 @@ public void releaseBlocking() { @Override public Future release() { - return vertx.getOrCreateContext().executeBlockingInternal(prom -> { + return vertx.getOrCreateContext().executeBlockingInternal(() -> { try { fileLock.release(); - prom.complete(); + return null; } catch (IOException e) { - prom.fail(new FileSystemException(e)); + throw new FileSystemException(e); } }); } diff --git a/src/main/java/io/vertx/core/file/impl/FileResolverImpl.java b/src/main/java/io/vertx/core/file/impl/FileResolverImpl.java index 65d6e0b919e..1550813dbfc 100644 --- a/src/main/java/io/vertx/core/file/impl/FileResolverImpl.java +++ b/src/main/java/io/vertx/core/file/impl/FileResolverImpl.java @@ -14,14 +14,23 @@ import io.netty.util.internal.PlatformDependent; import io.vertx.core.VertxException; import io.vertx.core.file.FileSystemOptions; +import io.vertx.core.impl.Utils; import io.vertx.core.spi.file.FileResolver; -import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.JarURLConnection; import java.net.URL; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; import java.util.Enumeration; +import java.util.List; +import java.util.jar.JarFile; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -50,7 +59,6 @@ public class FileResolverImpl implements FileResolver { private static final boolean NON_UNIX_FILE_SEP = File.separatorChar != '/'; private static final String JAR_URL_SEP = "!/"; - private final File cwd; private final boolean enableCaching; private final boolean enableCPResolving; private final FileCache cache; @@ -68,13 +76,6 @@ public FileResolverImpl(FileSystemOptions fileSystemOptions) { } else { cache = null; } - - String cwdOverride = System.getProperty("vertx.cwd"); - if (cwdOverride != null) { - cwd = new File(cwdOverride).getAbsoluteFile(); - } else { - cwd = null; - } } public String cacheDir() { @@ -102,20 +103,14 @@ public void close() throws IOException { public File resolveFile(String fileName) { // First look for file with that name on disk File file = new File(fileName); - File resolved; boolean absolute = file.isAbsolute(); - if (cwd != null && !absolute) { - resolved = new File(cwd, fileName); - } else { - resolved = file; - } if (this.cache == null) { - return resolved; + return file; } // We need to synchronized here to avoid 2 different threads to copy the file to the cache directory and so // corrupting the content. - if (!resolved.exists()) { + if (!file.exists()) { synchronized (cache) { // When an absolute file is here, if it falls under the cache directory, then it should be made relative as it // could mean that a previous resolution has been used to resolve a non local file system resource @@ -275,70 +270,124 @@ private File unpackFromFileURL(URL url, String fileName, ClassLoader cl) { return cacheFile; } - private File unpackFromJarURL(URL url, String fileName, ClassLoader cl) { - ZipFile zip = null; - try { - String path = url.getPath(); - int idx1 = path.lastIndexOf(".jar!"); - if (idx1 == -1) { - idx1 = path.lastIndexOf(".zip!"); - } - int idx2 = path.lastIndexOf(".jar!", idx1 - 1); - if (idx2 == -1) { - idx2 = path.lastIndexOf(".zip!", idx1 - 1); - } - if (idx2 == -1) { - File file = new File(decodeURIComponent(path.substring(5, idx1 + 4), false)); - zip = new ZipFile(file); + /** + * Parse the list of entries of a URL assuming the URL is a jar URL. + * + *

    + *
  • when the URL is a nested file within the archive, the list is the jar entry
  • + *
  • when the URL is a nested file within a nested file within the archive, the list is jar entry followed by the jar entry of the nested archive
  • + *
  • and so on.
  • + *
+ * + * @param url the URL + * @return the list of entries + */ + private List listOfEntries(URL url) { + String path = url.getPath(); + List list = new ArrayList<>(); + int last = path.length(); + for (int i = path.length() - 2; i >= 0;) { + if (path.charAt(i) == '!' && path.charAt(i + 1) == '/') { + list.add(path.substring(2 + i, last)); + last = i; + i -= 2; } else { - String s = path.substring(idx2 + 6, idx1 + 4); - File file = resolveFile(s); - zip = new ZipFile(file); + i--; } + } + return list; + } - String inJarPath = path.substring(idx1 + 6); - StringBuilder prefixBuilder = new StringBuilder(); - int first = 0; - int second; - int len = JAR_URL_SEP.length(); - while ((second = inJarPath.indexOf(JAR_URL_SEP, first)) >= 0) { - prefixBuilder.append(inJarPath, first, second).append("/"); - first = second + len; - } - String prefix = prefixBuilder.toString(); - Enumeration entries = zip.entries(); - String prefixCheck = prefix.isEmpty() ? fileName : prefix + fileName; - while (entries.hasMoreElements()) { - ZipEntry entry = entries.nextElement(); - String name = entry.getName(); - if (name.startsWith(prefixCheck)) { - String p = prefix.isEmpty() ? name : name.substring(prefix.length()); - if (name.endsWith("/")) { - // Directory - cache.cacheDir(p); - } else { - try (InputStream is = zip.getInputStream(entry)) { - cache.cacheFile(p, is, !enableCaching); + private File unpackFromJarURL(URL url, String fileName, ClassLoader cl) { + try { + List listOfEntries = listOfEntries(url); + switch (listOfEntries.size()) { + case 1: { + JarURLConnection conn = (JarURLConnection) url.openConnection(); + if (conn.getContentLength() == -1) { + // the entry is a directory, so, it does not really exist. + // which means we cannot get the JarFile from the connection. + + // In general, we should not be here, as the URL should be a directory, and we should not have a JarURLConnection. + // However, classloader implementation may provide an URL. In this case, we should not try to get the JarFile + // as it does not exist (or is not valid), and it will throw an exception. + + // We still retrieve it from the cache if it exists (got unzipped before or concurrently) + return cache.getFile(fileName); + } + ZipFile zip = conn.getJarFile(); + try { + extractFilesFromJarFile(zip, fileName); + } finally { + if (!conn.getUseCaches()) { + zip.close(); } } - + break; } + case 2: + URL nestedURL = cl.getResource(listOfEntries.get(1)); + if (nestedURL != null && nestedURL.getProtocol().equals("jar")) { + File root = unpackFromJarURL(nestedURL, listOfEntries.get(1), cl); + if (root.isDirectory()) { + // jar:file:/path/to/nesting.jar!/xxx-inf/classes + // we need to unpack xxx-inf/classes and then copy the content as is in the cache + Path path = root.toPath(); + Files.walkFileTree(path, new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + Path relative = path.relativize(dir); + cache.cacheDir(relative.toString()); + return FileVisitResult.CONTINUE; + } + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Path relative = path.relativize(file); + cache.cacheFile(relative.toString(), file.toFile(), false); + return FileVisitResult.CONTINUE; + } + }); + } else { + // jar:file:/path/to/nesting.jar!/path/to/nested.jar + try (ZipFile zip = new ZipFile(root)) { + extractFilesFromJarFile(zip, fileName); + } + } + } else { + throw new VertxException("Unexpected nested url : " + nestedURL); + } + break; + default: + throw new VertxException("Nesting more than two levels is not supported"); } } catch (IOException e) { throw new VertxException(FileSystemImpl.getFileAccessErrorMessage("unpack", url.toString()), e); - } finally { - closeQuietly(zip); } - return cache.getFile(fileName); } - private void closeQuietly(Closeable zip) { - if (zip != null) { - try { - zip.close(); - } catch (IOException e) { - // Ignored. + /** + * Extract a subset of the entries to the cache. + */ + private void extractFilesFromJarFile(ZipFile zip, String entryFilter) throws IOException { + Enumeration entries = zip.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + String name = entry.getName(); + int len = name.length(); + if (len == 0) { + return; + } + if (name.charAt(len - 1) != ' ' || !Utils.isWindows()) { + if (name.startsWith(entryFilter)) { + if (name.charAt(len - 1) == '/') { + cache.cacheDir(name); + } else { + try (InputStream is = zip.getInputStream(entry)) { + cache.cacheFile(name, is, !enableCaching); + } + } + } } } } diff --git a/src/main/java/io/vertx/core/file/impl/FileSystemImpl.java b/src/main/java/io/vertx/core/file/impl/FileSystemImpl.java index 1f960266ed4..9894bc94df7 100644 --- a/src/main/java/io/vertx/core/file/impl/FileSystemImpl.java +++ b/src/main/java/io/vertx/core/file/impl/FileSystemImpl.java @@ -12,10 +12,7 @@ package io.vertx.core.file.impl; import io.vertx.codegen.annotations.Nullable; -import io.vertx.core.AsyncResult; import io.vertx.core.Future; -import io.vertx.core.Handler; -import io.vertx.core.Promise; import io.vertx.core.buffer.Buffer; import io.vertx.core.file.AsyncFile; import io.vertx.core.file.CopyOptions; @@ -56,6 +53,7 @@ import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.concurrent.Callable; import java.util.regex.Pattern; /** @@ -958,7 +956,7 @@ public FileSystemProps perform() { try { Path target = vertx.resolveFile(path).toPath(); FileStore fs = Files.getFileStore(target); - return new FileSystemPropsImpl(fs.getTotalSpace(), fs.getUnallocatedSpace(), fs.getUsableSpace()); + return new FileSystemPropsImpl(fs.name(), fs.getTotalSpace(), fs.getUnallocatedSpace(), fs.getUsableSpace()); } catch (IOException e) { throw new FileSystemException(getFileAccessErrorMessage("analyse", path), e); } @@ -966,7 +964,7 @@ public FileSystemProps perform() { }; } - protected abstract class BlockingAction implements Handler> { + protected abstract class BlockingAction implements Callable { protected final ContextInternal context; @@ -982,13 +980,8 @@ public Future run() { } @Override - public void handle(Promise fut) { - try { - T result = perform(); - fut.complete(result); - } catch (Exception e) { - fut.fail(e); - } + public T call() throws Exception { + return perform(); } public abstract T perform(); diff --git a/src/main/java/io/vertx/core/file/impl/FileSystemPropsImpl.java b/src/main/java/io/vertx/core/file/impl/FileSystemPropsImpl.java index 0b4311194e8..7d68e24f1ee 100644 --- a/src/main/java/io/vertx/core/file/impl/FileSystemPropsImpl.java +++ b/src/main/java/io/vertx/core/file/impl/FileSystemPropsImpl.java @@ -15,16 +15,23 @@ public class FileSystemPropsImpl implements FileSystemProps { + private final String name; private final long totalSpace; private final long unallocatedSpace; private final long usableSpace; - public FileSystemPropsImpl(long totalSpace, long unallocatedSpace, long usableSpace) { + public FileSystemPropsImpl(String name, long totalSpace, long unallocatedSpace, long usableSpace) { + this.name = name; this.totalSpace = totalSpace; this.unallocatedSpace = unallocatedSpace; this.usableSpace = usableSpace; } + @Override + public String name() { + return name; + } + @Override public long totalSpace() { return totalSpace; diff --git a/src/main/java/io/vertx/core/http/ClientWebSocket.java b/src/main/java/io/vertx/core/http/ClientWebSocket.java new file mode 100644 index 00000000000..942e98f7822 --- /dev/null +++ b/src/main/java/io/vertx/core/http/ClientWebSocket.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.http; + +import io.vertx.codegen.annotations.Nullable; +import io.vertx.codegen.annotations.VertxGen; +import io.vertx.core.Future; +import io.vertx.core.Handler; +import io.vertx.core.MultiMap; +import io.vertx.core.buffer.Buffer; + +import java.util.List; + +/** + * Represents a client-side WebSocket. + * + * @author Tim Fox + */ +@VertxGen +public interface ClientWebSocket extends WebSocket { + + /** + * Connect this WebSocket with the specified options. + * + * @param options the request options + * @return a future notified when the WebSocket when connected + */ + Future connect(WebSocketConnectOptions options); + + /** + * Connect this WebSocket to the specified port, host and relative request URI. + * + * @param port the port + * @param host the host + * @param requestURI the relative URI + * @return a future notified when the WebSocket when connected + */ + default Future connect(int port, String host, String requestURI) { + return connect(new WebSocketConnectOptions().setURI(requestURI).setHost(host).setPort(port)); + } + + /** + * Connect this WebSocket to the host and relative request URI and default port. + * + * @param host the host + * @param requestURI the relative URI + * @return a future notified when the WebSocket when connected + */ + default Future connect(String host, String requestURI) { + return connect(new WebSocketConnectOptions().setHost(host).setURI(requestURI)); + } + + /** + * Connect this WebSocket at the relative request URI using the default host and port. + * + * @param requestURI the relative URI + * @return a future notified when the WebSocket when connected + */ + default Future connect(String requestURI) { + return connect(new WebSocketConnectOptions().setURI(requestURI)); + } + + @Override + ClientWebSocket handler(Handler handler); + + @Override + ClientWebSocket endHandler(Handler endHandler); + + @Override + ClientWebSocket drainHandler(Handler handler); + + @Override + ClientWebSocket closeHandler(Handler handler); + + @Override + ClientWebSocket frameHandler(Handler handler); + + @Override + ClientWebSocket textMessageHandler(@Nullable Handler handler); + + @Override + ClientWebSocket binaryMessageHandler(@Nullable Handler handler); + + @Override + ClientWebSocket pongHandler(@Nullable Handler handler); + + @Override + ClientWebSocket exceptionHandler(Handler handler); + +} diff --git a/src/main/java/io/vertx/core/http/GoAway.java b/src/main/java/io/vertx/core/http/GoAway.java index 108954d62a5..ad3568df55d 100644 --- a/src/main/java/io/vertx/core/http/GoAway.java +++ b/src/main/java/io/vertx/core/http/GoAway.java @@ -12,6 +12,7 @@ package io.vertx.core.http; import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.buffer.Buffer; import io.vertx.core.json.JsonObject; @@ -20,7 +21,8 @@ * * @author Julien Viet */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public class GoAway { private long errorCode; diff --git a/src/main/java/io/vertx/core/http/Http2Settings.java b/src/main/java/io/vertx/core/http/Http2Settings.java index da632482ddc..bbd12d81932 100644 --- a/src/main/java/io/vertx/core/http/Http2Settings.java +++ b/src/main/java/io/vertx/core/http/Http2Settings.java @@ -14,6 +14,7 @@ import io.netty.handler.codec.http2.Http2CodecUtil; import io.vertx.codegen.annotations.DataObject; import io.vertx.codegen.annotations.GenIgnore; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.impl.Arguments; import io.vertx.core.json.JsonObject; @@ -28,7 +29,8 @@ * * @author Julien Viet */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public class Http2Settings { /** diff --git a/src/main/java/io/vertx/core/http/HttpClient.java b/src/main/java/io/vertx/core/http/HttpClient.java index ad7dee49d96..fc3bd9aaaaa 100644 --- a/src/main/java/io/vertx/core/http/HttpClient.java +++ b/src/main/java/io/vertx/core/http/HttpClient.java @@ -11,18 +11,11 @@ package io.vertx.core.http; -import io.vertx.codegen.annotations.Fluent; -import io.vertx.codegen.annotations.GenIgnore; import io.vertx.codegen.annotations.VertxGen; import io.vertx.core.Future; -import io.vertx.core.Handler; -import io.vertx.core.MultiMap; -import io.vertx.core.metrics.Measured; -import io.vertx.core.net.SSLOptions; +import io.vertx.core.net.ClientSSLOptions; -import java.util.List; import java.util.concurrent.TimeUnit; -import java.util.function.Function; /** * An asynchronous HTTP client. @@ -53,7 +46,7 @@ * @author Tim Fox */ @VertxGen -public interface HttpClient extends Measured { +public interface HttpClient extends io.vertx.core.metrics.Measured { /** * Create an HTTP request to send to the server. @@ -72,7 +65,9 @@ public interface HttpClient extends Measured { * @param requestURI the relative URI * @return a future notified when the request is ready to be sent */ - Future request(HttpMethod method, int port, String host, String requestURI); + default Future request(HttpMethod method, int port, String host, String requestURI) { + return request(new RequestOptions().setMethod(method).setPort(port).setHost(host).setURI(requestURI)); + } /** * Create an HTTP request to send to the server at the {@code host} and default port. @@ -82,7 +77,9 @@ public interface HttpClient extends Measured { * @param requestURI the relative URI * @return a future notified when the request is ready to be sent */ - Future request(HttpMethod method, String host, String requestURI); + default Future request(HttpMethod method, String host, String requestURI) { + return request(new RequestOptions().setMethod(method).setHost(host).setURI(requestURI)); + } /** * Create an HTTP request to send to the server at the default host and port. @@ -91,103 +88,12 @@ public interface HttpClient extends Measured { * @param requestURI the relative URI * @return a future notified when the request is ready to be sent */ - Future request(HttpMethod method, String requestURI); - - /** - * Connect a WebSocket to the specified port, host and relative request URI. - * - * @param port the port - * @param host the host - * @param requestURI the relative URI - * @return a future notified when the WebSocket when connected - */ - Future webSocket(int port, String host, String requestURI); - - /** - * Connect a WebSocket to the host and relative request URI and default port. - * - * @param host the host - * @param requestURI the relative URI - * @return a future notified when the WebSocket when connected - */ - Future webSocket(String host, String requestURI); - - /** - * Connect a WebSocket at the relative request URI using the default host and port. - * - * @param requestURI the relative URI - * @return a future notified when the WebSocket when connected - */ - Future webSocket(String requestURI); - - /** - * Connect a WebSocket with the specified options. - * - * @param options the request options - * @return a future notified when the WebSocket when connected - */ - Future webSocket(WebSocketConnectOptions options); - - /** - * Connect a WebSocket with the specified absolute url, with the specified headers, using - * the specified version of WebSockets, and the specified WebSocket sub protocols. - * - * @param url the absolute url - * @param headers the headers - * @param version the WebSocket version - * @param subProtocols the subprotocols to use - * @return a future notified when the WebSocket when connected - */ - Future webSocketAbs(String url, MultiMap headers, WebsocketVersion version, List subProtocols); - - /** - * Update the client SSL options. - * - * Update only happens if the SSL options is valid. - * - * @param options the new SSL options - * @return a future signaling the update success - */ - Future updateSSLOptions(SSLOptions options); - - /** - * Set a connection handler for the client. This handler is called when a new connection is established. - * - * @return a reference to this, so the API can be used fluently - */ - @Fluent - HttpClient connectionHandler(Handler handler); - - /** - * Set a redirect handler for the http client. - *

- * The redirect handler is called when a {@code 3xx} response is received and the request is configured to - * follow redirects with {@link HttpClientRequest#setFollowRedirects(boolean)}. - *

- * The redirect handler is passed the {@link HttpClientResponse}, it can return an {@link HttpClientRequest} or {@code null}. - *

    - *
  • when null is returned, the original response is processed by the original request response handler
  • - *
  • when a new {@code Future} is returned, the client will send this new request
  • - *
- * The new request will get a copy of the previous request headers unless headers are set. In this case, - * the client assumes that the redirect handler exclusively managers the headers of the new request. - *

- * The handler must return a {@code Future} unsent so the client can further configure it and send it. - * - * @param handler the new redirect handler - * @return a reference to this, so the API can be used fluently - */ - @Fluent - HttpClient redirectHandler(Function> handler); - - /** - * @return the current redirect handler. - */ - @GenIgnore - Function> redirectHandler(); + default Future request(HttpMethod method, String requestURI) { + return request(new RequestOptions().setMethod(method).setURI(requestURI)); + } /** - * Close the client immediately ({@code close(0, TimeUnit.SECONDS}). + * Close the client immediately ({@code shutdown(0, TimeUnit.SECONDS}). * * @return a future notified when the client is closed */ @@ -211,4 +117,32 @@ default Future close() { */ Future close(long timeout, TimeUnit timeUnit); + /** + *

Update the client with new SSL {@code options}, the update happens if the options object is valid and different + * from the existing options object. + * + *

The boolean succeeded future result indicates whether the update occurred. + * + * @param options the new SSL options + * @return a future signaling the update success + */ + default Future updateSSLOptions(ClientSSLOptions options) { + return updateSSLOptions(options, false); + } + + /** + *

Update the client with new SSL {@code options}, the update happens if the options object is valid and different + * from the existing options object. + * + *

The {@code options} object is compared using its {@code equals} method against the existing options to prevent + * an update when the objects are equals since loading options can be costly, this can happen for share TCP servers. + * When object are equals, setting {@code force} to {@code true} forces the update. + * + *

The boolean succeeded future result indicates whether the update occurred. + * + * @param options the new SSL options + * @param force force the update when options are equals + * @return a future signaling the update success + */ + Future updateSSLOptions(ClientSSLOptions options, boolean force); } diff --git a/src/main/java/io/vertx/core/http/HttpClientBuilder.java b/src/main/java/io/vertx/core/http/HttpClientBuilder.java new file mode 100644 index 00000000000..f62dcf39f5b --- /dev/null +++ b/src/main/java/io/vertx/core/http/HttpClientBuilder.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.http; + +import io.vertx.codegen.annotations.Fluent; +import io.vertx.codegen.annotations.GenIgnore; +import io.vertx.codegen.annotations.VertxGen; +import io.vertx.core.Future; +import io.vertx.core.Handler; +import io.vertx.core.loadbalancing.LoadBalancer; +import io.vertx.core.net.AddressResolver; + +import java.util.function.Function; + +/** + * A builder for {@link HttpClient}. + * + * @author Julien Viet + */ +@VertxGen +public interface HttpClientBuilder { + + /** + * Configure the client options. + * @param options the client options + * @return a reference to this, so the API can be used fluently + */ + @Fluent + HttpClientBuilder with(HttpClientOptions options); + + /** + * Configure the client with the given pool {@code options}. + * @param options the pool options + * @return a reference to this, so the API can be used fluently + */ + @Fluent + HttpClientBuilder with(PoolOptions options); + + /** + * Set a connection handler for the client. This handler is called when a new connection is established. + * + * @return a reference to this, so the API can be used fluently + */ + @Fluent + HttpClientBuilder withConnectHandler(Handler handler); + + /** + * Set a redirect handler for the http client. + *

+ * The redirect handler is called when a {@code 3xx} response is received and the request is configured to + * follow redirects with {@link HttpClientRequest#setFollowRedirects(boolean)}. + *

+ * The redirect handler is passed the {@link HttpClientResponse}, it can return an {@link HttpClientRequest} or {@code null}. + *

    + *
  • when null is returned, the original response is processed by the original request response handler
  • + *
  • when a new {@code Future} is returned, the client will send this new request
  • + *
+ * The new request will get a copy of the previous request headers unless headers are set. In this case, + * the client assumes that the redirect handler exclusively managers the headers of the new request. + *

+ * The handler must return a {@code Future} unsent so the client can further configure it and send it. + * + * @param handler the new redirect handler + * @return a reference to this, so the API can be used fluently + */ + @Fluent + HttpClientBuilder withRedirectHandler(Function> handler); + + /** + * Configure the client to use a specific address lookup. + * + * @param lookup the address lookup + */ + @GenIgnore(GenIgnore.PERMITTED_TYPE) + HttpClientBuilder withAddressResolver(AddressResolver lookup); + + /** + * Configure the client to use a load balancer. + * + * @param loadBalancer the load balancer + */ + @GenIgnore(GenIgnore.PERMITTED_TYPE) + HttpClientBuilder withLoadBalancer(LoadBalancer loadBalancer); + + /** + * Build and return the client. + * @return the client as configured by this builder + */ + HttpClient build(); + +} diff --git a/src/main/java/io/vertx/core/http/HttpClientOptions.java b/src/main/java/io/vertx/core/http/HttpClientOptions.java index d50cb3af581..79d0f459402 100755 --- a/src/main/java/io/vertx/core/http/HttpClientOptions.java +++ b/src/main/java/io/vertx/core/http/HttpClientOptions.java @@ -11,19 +11,16 @@ package io.vertx.core.http; +import io.netty.handler.logging.ByteBufFormat; import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.buffer.Buffer; import io.vertx.core.impl.Arguments; import io.vertx.core.json.JsonObject; import io.vertx.core.net.*; import io.vertx.core.tracing.TracingPolicy; -import io.netty.handler.logging.ByteBufFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.concurrent.TimeUnit; /** @@ -31,19 +28,10 @@ * * @author Tim Fox */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public class HttpClientOptions extends ClientOptionsBase { - /** - * The default maximum number of HTTP/1 connections a client will pool = 5 - */ - public static final int DEFAULT_MAX_POOL_SIZE = 5; - - /** - * The default maximum number of connections an HTTP/2 client will pool = 1 - */ - public static final int DEFAULT_HTTP2_MAX_POOL_SIZE = 1; - /** * The default maximum number of concurrent streams per connection for HTTP/2 = -1 */ @@ -80,31 +68,15 @@ public class HttpClientOptions extends ClientOptionsBase { public static final int DEFAULT_KEEP_ALIVE_TIMEOUT = 60; /** - * Default value of whether the client will attempt to use compression = {@code false} + * Whether the client should send requests with an {@code accepting-encoding} header set to a compression algorithm by default = {@code false} */ - public static final boolean DEFAULT_TRY_USE_COMPRESSION = false; + public static final boolean DEFAULT_DECOMPRESSION_SUPPORTED = false; /** * Default value of whether hostname verification (for SSL/TLS) is enabled = {@code true} */ public static final boolean DEFAULT_VERIFY_HOST = true; - /** - * The default value for maximum WebSocket frame size = 65536 bytes - */ - public static final int DEFAULT_MAX_WEBSOCKET_FRAME_SIZE = 65536; - - /** - * The default value for maximum WebSocket messages (could be assembled from multiple frames) is 4 full frames - * worth of data - */ - public static final int DEFAULT_MAX_WEBSOCKET_MESSAGE_SIZE = 65536 * 4; - - /** - * The default value for the maximum number of WebSocket = 50 - */ - public static final int DEFAULT_MAX_WEBSOCKETS = 50; - /** * The default value for host name = "localhost" */ @@ -135,11 +107,6 @@ public class HttpClientOptions extends ClientOptionsBase { */ public static final int DEFAULT_MAX_HEADER_SIZE = 8192; - /** - * Default max wait queue size = -1 (unbounded) - */ - public static final int DEFAULT_MAX_WAIT_QUEUE_SIZE = -1; - /** * Default Application-Layer Protocol Negotiation versions = [] (automatic according to protocol version) */ @@ -155,11 +122,6 @@ public class HttpClientOptions extends ClientOptionsBase { */ public static final boolean DEFAULT_HTTP2_CLEAR_TEXT_UPGRADE_WITH_PREFLIGHT_REQUEST = false; - /** - * Default WebSocket masked bit is true as depicted by RFC = {@code false} - */ - public static final boolean DEFAULT_SEND_UNMASKED_FRAMES = false; - /* * Default max redirect = 16 */ @@ -175,46 +137,6 @@ public class HttpClientOptions extends ClientOptionsBase { */ public static final int DEFAULT_DECODER_INITIAL_BUFFER_SIZE = 128; - /** - * Default offer WebSocket per-frame deflate compression extension = {@code false} - */ - public static final boolean DEFAULT_TRY_USE_PER_FRAME_WEBSOCKET_COMPRESSION = false; - - /** - * Default offer WebSocket per-message deflate compression extension = {@code false} - */ - public static final boolean DEFAULT_TRY_USE_PER_MESSAGE_WEBSOCKET_COMPRESSION = false; - - /** - * Default WebSocket deflate compression level = 6 - */ - public static final int DEFAULT_WEBSOCKET_COMPRESSION_LEVEL = 6; - - /** - * Default offering of the {@code server_no_context_takeover} WebSocket parameter deflate compression extension = {@code false} - */ - public static final boolean DEFAULT_WEBSOCKET_ALLOW_CLIENT_NO_CONTEXT = false; - - /** - * Default offering of the {@code client_no_context_takeover} WebSocket parameter deflate compression extension = {@code false} - */ - public static final boolean DEFAULT_WEBSOCKET_REQUEST_SERVER_NO_CONTEXT = false; - - /** - * Default pool cleaner period = 1000 ms (1 second) - */ - public static final int DEFAULT_POOL_CLEANER_PERIOD = 1000; - - /** - * Default pool event loop size = 0 (reuse current event-loop) - */ - public static final int DEFAULT_POOL_EVENT_LOOP_SIZE = 0; - - /** - * Default WebSocket closing timeout = 10 second - */ - public static final int DEFAULT_WEBSOCKET_CLOSING_TIMEOUT = 10; - /** * Default tracing control = {@link TracingPolicy#PROPAGATE} */ @@ -231,45 +153,29 @@ public class HttpClientOptions extends ClientOptionsBase { public static final String DEFAULT_NAME = "__vertx.DEFAULT"; private boolean verifyHost = true; - private int maxPoolSize; private boolean keepAlive; private int keepAliveTimeout; private int pipeliningLimit; private boolean pipelining; - private int http2MaxPoolSize; private int http2MultiplexingLimit; private int http2ConnectionWindowSize; private int http2KeepAliveTimeout; - private int poolCleanerPeriod; - private int poolEventLoopSize; - private boolean tryUseCompression; - private int maxWebSocketFrameSize; - private int maxWebSocketMessageSize; - private int maxWebSockets; + private boolean decompressionSupported; private String defaultHost; private int defaultPort; private HttpVersion protocolVersion; private int maxChunkSize; private int maxInitialLineLength; private int maxHeaderSize; - private int maxWaitQueueSize; private Http2Settings initialSettings; private List alpnVersions; private boolean http2ClearTextUpgrade; private boolean http2ClearTextUpgradeWithPreflightRequest; - private boolean sendUnmaskedFrames; private int maxRedirects; private boolean forceSni; private int decoderInitialBufferSize; - private boolean tryUsePerFrameWebSocketCompression; - private boolean tryUsePerMessageWebSocketCompression; - private int webSocketCompressionLevel; - private boolean webSocketAllowClientNoContext; - private boolean webSocketRequestServerNoContext; - private int webSocketClosingTimeout; - private TracingPolicy tracingPolicy; private boolean shared; @@ -283,6 +189,16 @@ public HttpClientOptions() { init(); } + /** + * Copy constructor + * + * @param other the options to copy + */ + public HttpClientOptions(ClientOptionsBase other) { + super(other); + init(); + } + /** * Copy constructor * @@ -291,42 +207,27 @@ public HttpClientOptions() { public HttpClientOptions(HttpClientOptions other) { super(other); this.verifyHost = other.isVerifyHost(); - this.maxPoolSize = other.getMaxPoolSize(); this.keepAlive = other.isKeepAlive(); this.keepAliveTimeout = other.getKeepAliveTimeout(); this.pipelining = other.isPipelining(); this.pipeliningLimit = other.getPipeliningLimit(); - this.http2MaxPoolSize = other.getHttp2MaxPoolSize(); this.http2MultiplexingLimit = other.http2MultiplexingLimit; this.http2ConnectionWindowSize = other.http2ConnectionWindowSize; this.http2KeepAliveTimeout = other.getHttp2KeepAliveTimeout(); - this.tryUseCompression = other.isTryUseCompression(); - this.maxWebSocketFrameSize = other.maxWebSocketFrameSize; - this.maxWebSocketMessageSize = other.maxWebSocketMessageSize; - this.maxWebSockets = other.maxWebSockets; + this.decompressionSupported = other.decompressionSupported; this.defaultHost = other.defaultHost; this.defaultPort = other.defaultPort; this.protocolVersion = other.protocolVersion; this.maxChunkSize = other.maxChunkSize; this.maxInitialLineLength = other.getMaxInitialLineLength(); this.maxHeaderSize = other.getMaxHeaderSize(); - this.maxWaitQueueSize = other.maxWaitQueueSize; this.initialSettings = other.initialSettings != null ? new Http2Settings(other.initialSettings) : null; this.alpnVersions = other.alpnVersions != null ? new ArrayList<>(other.alpnVersions) : null; this.http2ClearTextUpgrade = other.http2ClearTextUpgrade; this.http2ClearTextUpgradeWithPreflightRequest = other.http2ClearTextUpgradeWithPreflightRequest; - this.sendUnmaskedFrames = other.isSendUnmaskedFrames(); this.maxRedirects = other.maxRedirects; this.forceSni = other.forceSni; this.decoderInitialBufferSize = other.getDecoderInitialBufferSize(); - this.poolCleanerPeriod = other.getPoolCleanerPeriod(); - this.poolEventLoopSize = other.getPoolEventLoopSize(); - this.tryUsePerFrameWebSocketCompression = other.tryUsePerFrameWebSocketCompression; - this.tryUsePerMessageWebSocketCompression = other.tryUsePerMessageWebSocketCompression; - this.webSocketAllowClientNoContext = other.webSocketAllowClientNoContext; - this.webSocketCompressionLevel = other.webSocketCompressionLevel; - this.webSocketRequestServerNoContext = other.webSocketRequestServerNoContext; - this.webSocketClosingTimeout = other.webSocketClosingTimeout; this.tracingPolicy = other.tracingPolicy; this.shared = other.shared; this.name = other.name; @@ -356,42 +257,27 @@ public JsonObject toJson() { private void init() { verifyHost = DEFAULT_VERIFY_HOST; - maxPoolSize = DEFAULT_MAX_POOL_SIZE; keepAlive = DEFAULT_KEEP_ALIVE; keepAliveTimeout = DEFAULT_KEEP_ALIVE_TIMEOUT; pipelining = DEFAULT_PIPELINING; pipeliningLimit = DEFAULT_PIPELINING_LIMIT; http2MultiplexingLimit = DEFAULT_HTTP2_MULTIPLEXING_LIMIT; - http2MaxPoolSize = DEFAULT_HTTP2_MAX_POOL_SIZE; http2ConnectionWindowSize = DEFAULT_HTTP2_CONNECTION_WINDOW_SIZE; http2KeepAliveTimeout = DEFAULT_HTTP2_KEEP_ALIVE_TIMEOUT; - tryUseCompression = DEFAULT_TRY_USE_COMPRESSION; - maxWebSocketFrameSize = DEFAULT_MAX_WEBSOCKET_FRAME_SIZE; - maxWebSocketMessageSize = DEFAULT_MAX_WEBSOCKET_MESSAGE_SIZE; - maxWebSockets = DEFAULT_MAX_WEBSOCKETS; + decompressionSupported = DEFAULT_DECOMPRESSION_SUPPORTED; defaultHost = DEFAULT_DEFAULT_HOST; defaultPort = DEFAULT_DEFAULT_PORT; protocolVersion = DEFAULT_PROTOCOL_VERSION; maxChunkSize = DEFAULT_MAX_CHUNK_SIZE; maxInitialLineLength = DEFAULT_MAX_INITIAL_LINE_LENGTH; maxHeaderSize = DEFAULT_MAX_HEADER_SIZE; - maxWaitQueueSize = DEFAULT_MAX_WAIT_QUEUE_SIZE; initialSettings = new Http2Settings(); alpnVersions = new ArrayList<>(DEFAULT_ALPN_VERSIONS); http2ClearTextUpgrade = DEFAULT_HTTP2_CLEAR_TEXT_UPGRADE; http2ClearTextUpgradeWithPreflightRequest = DEFAULT_HTTP2_CLEAR_TEXT_UPGRADE_WITH_PREFLIGHT_REQUEST; - sendUnmaskedFrames = DEFAULT_SEND_UNMASKED_FRAMES; maxRedirects = DEFAULT_MAX_REDIRECTS; forceSni = DEFAULT_FORCE_SNI; decoderInitialBufferSize = DEFAULT_DECODER_INITIAL_BUFFER_SIZE; - tryUsePerFrameWebSocketCompression = DEFAULT_TRY_USE_PER_FRAME_WEBSOCKET_COMPRESSION; - tryUsePerMessageWebSocketCompression = DEFAULT_TRY_USE_PER_MESSAGE_WEBSOCKET_COMPRESSION; - webSocketCompressionLevel = DEFAULT_WEBSOCKET_COMPRESSION_LEVEL; - webSocketAllowClientNoContext = DEFAULT_WEBSOCKET_ALLOW_CLIENT_NO_CONTEXT; - webSocketRequestServerNoContext = DEFAULT_WEBSOCKET_REQUEST_SERVER_NO_CONTEXT; - webSocketClosingTimeout = DEFAULT_WEBSOCKET_CLOSING_TIMEOUT; - poolCleanerPeriod = DEFAULT_POOL_CLEANER_PERIOD; - poolEventLoopSize = DEFAULT_POOL_EVENT_LOOP_SIZE; tracingPolicy = DEFAULT_TRACING_POLICY; shared = DEFAULT_SHARED; name = DEFAULT_NAME; @@ -481,44 +367,12 @@ public HttpClientOptions setKeyCertOptions(KeyCertOptions options) { return this; } - @Override - public HttpClientOptions setKeyStoreOptions(JksOptions options) { - super.setKeyStoreOptions(options); - return this; - } - - @Override - public HttpClientOptions setPfxKeyCertOptions(PfxOptions options) { - return (HttpClientOptions) super.setPfxKeyCertOptions(options); - } - @Override public HttpClientOptions setTrustOptions(TrustOptions options) { super.setTrustOptions(options); return this; } - @Override - public HttpClientOptions setPemKeyCertOptions(PemKeyCertOptions options) { - return (HttpClientOptions) super.setPemKeyCertOptions(options); - } - - @Override - public HttpClientOptions setTrustStoreOptions(JksOptions options) { - super.setTrustStoreOptions(options); - return this; - } - - @Override - public HttpClientOptions setPfxTrustOptions(PfxOptions options) { - return (HttpClientOptions) super.setPfxTrustOptions(options); - } - - @Override - public HttpClientOptions setPemTrustOptions(PemTrustOptions options) { - return (HttpClientOptions) super.setPemTrustOptions(options); - } - @Override public HttpClientOptions addEnabledCipherSuite(String suite) { super.addEnabledCipherSuite(suite); @@ -602,29 +456,6 @@ public HttpClientOptions setSslHandshakeTimeoutUnit(TimeUnit sslHandshakeTimeout return this; } - /** - * Get the maximum pool size for connections - * - * @return the maximum pool size - */ - public int getMaxPoolSize() { - return maxPoolSize; - } - - /** - * Set the maximum pool size for connections - * - * @param maxPoolSize the maximum pool size - * @return a reference to this, so the API can be used fluently - */ - public HttpClientOptions setMaxPoolSize(int maxPoolSize) { - if (maxPoolSize < 1) { - throw new IllegalArgumentException("maxPoolSize must be > 0"); - } - this.maxPoolSize = maxPoolSize; - return this; - } - /** * @return the maximum number of concurrent streams for an HTTP/2 connection, {@code -1} means * the value sent by the server @@ -652,29 +483,6 @@ public HttpClientOptions setHttp2MultiplexingLimit(int limit) { return this; } - /** - * Get the maximum pool size for HTTP/2 connections - * - * @return the maximum pool size - */ - public int getHttp2MaxPoolSize() { - return http2MaxPoolSize; - } - - /** - * Set the maximum pool size for HTTP/2 connections - * - * @param max the maximum pool size - * @return a reference to this, so the API can be used fluently - */ - public HttpClientOptions setHttp2MaxPoolSize(int max) { - if (max < 1) { - throw new IllegalArgumentException("http2MaxPoolSize must be > 0"); - } - this.http2MaxPoolSize = max; - return this; - } - /** * @return the default HTTP/2 connection window size */ @@ -829,109 +637,20 @@ public HttpClientOptions setVerifyHost(boolean verifyHost) { } /** - * Is compression enabled on the client? - * - * @return {@code true} if enabled - */ - public boolean isTryUseCompression() { - return tryUseCompression; - } - - /** - * Set whether compression is enabled - * - * @param tryUseCompression {@code true} if enabled - * @return a reference to this, so the API can be used fluently - */ - public HttpClientOptions setTryUseCompression(boolean tryUseCompression) { - this.tryUseCompression = tryUseCompression; - return this; - } - - - /** - * @return {@code true} when frame masking is skipped - */ - public boolean isSendUnmaskedFrames() { - return sendUnmaskedFrames; - } - - /** - * Set {@code true} when the client wants to skip frame masking. - *

- * You may want to set it {@code true} on server by server WebSocket communication: in this case you are by passing - * RFC6455 protocol. - *

- * It's {@code false} as default. - * - * @param sendUnmaskedFrames true if enabled - * @return a reference to this, so the API can be used fluently - */ - public HttpClientOptions setSendUnmaskedFrames(boolean sendUnmaskedFrames) { - this.sendUnmaskedFrames = sendUnmaskedFrames; - return this; - } - - /** - * Get the maximum WebSocket frame size to use - * - * @return the max WebSocket frame size - */ - public int getMaxWebSocketFrameSize() { - return maxWebSocketFrameSize; - } - - /** - * Set the max WebSocket frame size - * - * @param maxWebSocketFrameSize the max frame size, in bytes - * @return a reference to this, so the API can be used fluently - */ - public HttpClientOptions setMaxWebSocketFrameSize(int maxWebSocketFrameSize) { - this.maxWebSocketFrameSize = maxWebSocketFrameSize; - return this; - } - - /** - * Get the maximum WebSocket message size to use - * - * @return the max WebSocket message size - */ - public int getMaxWebSocketMessageSize() { - return maxWebSocketMessageSize; - } - - /** - * Set the max WebSocket message size - * - * @param maxWebSocketMessageSize the max message size, in bytes - * @return a reference to this, so the API can be used fluently - */ - public HttpClientOptions setMaxWebSocketMessageSize(int maxWebSocketMessageSize) { - this.maxWebSocketMessageSize = maxWebSocketMessageSize; - return this; - } - - /** - * Get the maximum of WebSockets per endpoint. - * - * @return the max number of WebSockets + * @return {@code true} if the client should send requests with an {@code accepting-encoding} header set to a compression algorithm, {@code false} otherwise */ - public int getMaxWebSockets() { - return maxWebSockets; + public boolean isDecompressionSupported() { + return decompressionSupported; } /** - * Set the max number of WebSockets per endpoint. + * Whether the client should send requests with an {@code accepting-encoding} header set to a compression algorithm. * - * @param maxWebSockets the max value + * @param decompressionSupported {@code true} if the client should send a request with an {@code accepting-encoding} header set to a compression algorithm, {@code false} otherwise * @return a reference to this, so the API can be used fluently */ - public HttpClientOptions setMaxWebSockets(int maxWebSockets) { - if (maxWebSockets == 0 || maxWebSockets < -1) { - throw new IllegalArgumentException("maxWebSockets must be > 0 or -1 (disabled)"); - } - this.maxWebSockets = maxWebSockets; + public HttpClientOptions setDecompressionSupported(boolean decompressionSupported) { + this.decompressionSupported = decompressionSupported; return this; } @@ -1050,25 +769,6 @@ public HttpClientOptions setMaxHeaderSize(int maxHeaderSize) { return this; } - /** - * Set the maximum requests allowed in the wait queue, any requests beyond the max size will result in - * a ConnectionPoolTooBusyException. If the value is set to a negative number then the queue will be unbounded. - * @param maxWaitQueueSize the maximum number of waiting requests - * @return a reference to this, so the API can be used fluently - */ - public HttpClientOptions setMaxWaitQueueSize(int maxWaitQueueSize) { - this.maxWaitQueueSize = maxWaitQueueSize; - return this; - } - - /** - * Returns the maximum wait queue size - * @return the maximum wait queue size - */ - public int getMaxWaitQueueSize() { - return maxWaitQueueSize; - } - /** * @return the initial HTTP/2 connection settings */ @@ -1097,16 +797,6 @@ public HttpClientOptions setSslEngineOptions(SSLEngineOptions sslEngineOptions) return (HttpClientOptions) super.setSslEngineOptions(sslEngineOptions); } - @Override - public HttpClientOptions setJdkSslEngineOptions(JdkSSLEngineOptions sslEngineOptions) { - return (HttpClientOptions) super.setSslEngineOptions(sslEngineOptions); - } - - @Override - public HttpClientOptions setOpenSslEngineOptions(OpenSSLEngineOptions sslEngineOptions) { - return (HttpClientOptions) super.setSslEngineOptions(sslEngineOptions); - } - /** * @return the list of protocol versions to provide during the Application-Layer Protocol Negotiation. When * the list is empty, the client provides a best effort list according to {@link #setProtocolVersion} @@ -1241,125 +931,6 @@ public HttpClientOptions setActivityLogDataFormat(ByteBufFormat activityLogDataF return (HttpClientOptions) super.setActivityLogDataFormat(activityLogDataFormat); } - /** - * Set whether the client will offer the WebSocket per-frame deflate compression extension. - * - * @param offer {@code true} to offer the per-frame deflate compression extension - * @return a reference to this, so the API can be used fluently - */ - public HttpClientOptions setTryUsePerFrameWebSocketCompression(boolean offer) { - this.tryUsePerFrameWebSocketCompression = offer; - return this; - } - - /** - * @return {@code true} when the WebSocket per-frame deflate compression extension will be offered - */ - public boolean getTryWebSocketDeflateFrameCompression() { - return this.tryUsePerFrameWebSocketCompression; - } - - /** - * Set whether the client will offer the WebSocket per-message deflate compression extension. - * - * @param offer {@code true} to offer the per-message deflate compression extension - * @return a reference to this, so the API can be used fluently - */ - public HttpClientOptions setTryUsePerMessageWebSocketCompression(boolean offer) { - this.tryUsePerMessageWebSocketCompression = offer; - return this; - } - - /** - * @return {@code true} when the WebSocket per-message deflate compression extension will be offered - */ - public boolean getTryUsePerMessageWebSocketCompression() { - return this.tryUsePerMessageWebSocketCompression; - } - - /** - * Set the WebSocket deflate compression level. - * - * @param compressionLevel the WebSocket deflate compression level - * @return a reference to this, so the API can be used fluently - */ - public HttpClientOptions setWebSocketCompressionLevel(int compressionLevel) { - this.webSocketCompressionLevel = compressionLevel; - return this; - } - - /** - * @return the WebSocket deflate compression level - */ - public int getWebSocketCompressionLevel() { - return this.webSocketCompressionLevel; - } - - /** - * Set whether the {@code client_no_context_takeover} parameter of the WebSocket per-message - * deflate compression extension will be offered. - * - * @param offer {@code true} to offer the {@code client_no_context_takeover} parameter - * @return a reference to this, so the API can be used fluently - */ - public HttpClientOptions setWebSocketCompressionAllowClientNoContext(boolean offer) { - this.webSocketAllowClientNoContext = offer; - return this; - } - - /** - * @return {@code true} when the {@code client_no_context_takeover} parameter for the WebSocket per-message - * deflate compression extension will be offered - */ - public boolean getWebSocketCompressionAllowClientNoContext() { - return this.webSocketAllowClientNoContext; - } - - /** - * Set whether the {@code server_no_context_takeover} parameter of the WebSocket per-message - * deflate compression extension will be offered. - * - * @param offer {@code true} to offer the {@code server_no_context_takeover} parameter - * @return a reference to this, so the API can be used fluently - */ - public HttpClientOptions setWebSocketCompressionRequestServerNoContext(boolean offer) { - this.webSocketRequestServerNoContext = offer; - return this; - } - - /** - * @return {@code true} when the {@code server_no_context_takeover} parameter for the WebSocket per-message - * deflate compression extension will be offered - */ - public boolean getWebSocketCompressionRequestServerNoContext() { - return this.webSocketRequestServerNoContext; - } - - /** - * @return the amount of time (in seconds) a client WebSocket will wait until it closes TCP connection after receiving a close frame - */ - public int getWebSocketClosingTimeout() { - return webSocketClosingTimeout; - } - - /** - * Set the amount of time a client WebSocket will wait until it closes the TCP connection after receiving a close frame. - * - *

When a WebSocket is closed, the server should close the TCP connection. This timeout will close - * the TCP connection on the client when it expires. - * - *

Set to {@code 0L} closes the TCP connection immediately after receiving the close frame. - * - *

Set to a negative value to disable it. - * - * @param webSocketClosingTimeout the timeout is seconds - * @return a reference to this, so the API can be used fluently - */ - public HttpClientOptions setWebSocketClosingTimeout(int webSocketClosingTimeout) { - this.webSocketClosingTimeout = webSocketClosingTimeout; - return this; - } - /** * @return the initial buffer size for the HTTP decoder */ @@ -1377,52 +948,6 @@ public HttpClientOptions setDecoderInitialBufferSize(int decoderInitialBufferSiz return this; } - /** - * @return the connection pool cleaner period in ms. - */ - public int getPoolCleanerPeriod() { - return poolCleanerPeriod; - } - - /** - * Set the connection pool cleaner period in milli seconds, a non positive value disables expiration checks and connections - * will remain in the pool until they are closed. - * - * @param poolCleanerPeriod the pool cleaner period - * @return a reference to this, so the API can be used fluently - */ - public HttpClientOptions setPoolCleanerPeriod(int poolCleanerPeriod) { - this.poolCleanerPeriod = poolCleanerPeriod; - return this; - } - - /** - * @return the max number of event-loop a pool will use, the default value is {@code 0} which implies - * to reuse the current event-loop - */ - public int getPoolEventLoopSize() { - return poolEventLoopSize; - } - - /** - * Set the number of event-loop the pool use. - * - *

    - *
  • when the size is {@code 0}, the client pool will use the current event-loop
  • - *
  • otherwise the client will create and use its own event loop
  • - *
- * - * The default size is {@code 0}. - * - * @param poolEventLoopSize the new size - * @return a reference to this, so the API can be used fluently - */ - public HttpClientOptions setPoolEventLoopSize(int poolEventLoopSize) { - Arguments.require(poolEventLoopSize >= 0, "poolEventLoopSize must be >= 0"); - this.poolEventLoopSize = poolEventLoopSize; - return this; - } - /** * @return the tracing policy */ diff --git a/src/main/java/io/vertx/core/http/HttpClientRequest.java b/src/main/java/io/vertx/core/http/HttpClientRequest.java index 78df6dc78c2..d68a6c2df78 100644 --- a/src/main/java/io/vertx/core/http/HttpClientRequest.java +++ b/src/main/java/io/vertx/core/http/HttpClientRequest.java @@ -12,15 +12,17 @@ package io.vertx.core.http; import io.vertx.codegen.annotations.*; -import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.MultiMap; import io.vertx.core.buffer.Buffer; +import io.vertx.core.net.HostAndPort; import io.vertx.core.net.NetSocket; import io.vertx.core.streams.ReadStream; import io.vertx.core.streams.WriteStream; +import java.util.function.Function; + /** * Represents a client-side HTTP request. *

@@ -63,37 +65,17 @@ public interface HttpClientRequest extends WriteStream { HttpClientRequest drainHandler(Handler handler); /** - * Set the host value of the HTTP/1.1 {@code host} header or HTTP/2 {@code authority} pseudo header - *

The initial value is the same than the server socket address host. - *

Keep in mind that changing this value won't change the actual server socket address for this request. - * - * @param host the host part of the HTTP/1.1 {@code host} header or HTTP/2 {@code authority} pseudo header - * @return a reference to this, so the API can be used fluently - */ - @Fluent - HttpClientRequest setHost(String host); - - /** - * @return the host value of the HTTP/1.1 {@code host} header or HTTP/2 {@code authority} pseudo header - */ - String getHost(); - - /** - * Set the port value of the HTTP/1.1 {@code host} header or HTTP/2 {@code authority} pseudo header + * Override the request authority, when using HTTP/1.x this overrides the request {@code host} header, when using + * HTTP/2 this sets the {@code authority} pseudo header. When the port is a negative value, the default + * scheme port will be used. * - *

Keep in mind that this won't change the actual server socket address for this request. - *

The initial value is the same than the server socket address port. + *

The default request authority is the server host and port when connecting to the server. * - * @param port the port part of the HTTP/1.1 {@code host} header or HTTP/2 {@code authority} pseudo header + * @param authority override the request authority * @return a reference to this, so the API can be used fluently */ @Fluent - HttpClientRequest setPort(int port); - - /** - * @return the port value of the HTTP/1.1 {@code host} header or HTTP/2 {@code authority} pseudo header - */ - int getPort(); + HttpClientRequest authority(HostAndPort authority); /** * Set the request to follow HTTP redirects up to {@link HttpClientOptions#getMaxRedirects()}. @@ -104,6 +86,11 @@ public interface HttpClientRequest extends WriteStream { @Fluent HttpClientRequest setFollowRedirects(boolean followRedirects); + /** + * @return whether HTTP redirections should be followed + */ + boolean isFollowRedirects(); + /** * Set the max number of HTTP redirects this request will follow. The default is {@code 0} which means * no redirects. @@ -114,6 +101,16 @@ public interface HttpClientRequest extends WriteStream { @Fluent HttpClientRequest setMaxRedirects(int maxRedirects); + /** + * @return the maximum number of HTTP redirections to follow + */ + int getMaxRedirects(); + + /** + * @return the number of followed redirections for the current HTTP request + */ + int numberOfRedirections(); + /** * If chunked is true then the request will be set into HTTP chunked mode * @@ -212,6 +209,19 @@ public interface HttpClientRequest extends WriteStream { @Fluent HttpClientRequest putHeader(CharSequence name, Iterable values); + /** + * Set the trace operation of this request. + * + * @param op the operation + * @return @return a reference to this, so the API can be used fluently + */ + HttpClientRequest traceOperation(String op); + + /** + * @return the trace operation of this request + */ + String traceOperation(); + /** * @return the HTTP version for this request */ @@ -258,6 +268,9 @@ public interface HttpClientRequest extends WriteStream { @Fluent HttpClientRequest earlyHintsHandler(@Nullable Handler handler); + @Fluent + HttpClientRequest redirectHandler(@Nullable Function> handler); + /** * Forces the head of the request to be written before {@link #end()} is called on the request or any data is * written to it. @@ -386,18 +399,15 @@ default Future send(ReadStream body) { Future end(); /** - * Set's the amount of time after which if the request does not return any data within the timeout period an - * {@link java.util.concurrent.TimeoutException} will be passed to the exception handler (if provided) and - * the request will be closed. - *

- * Calling this method more than once has the effect of canceling any existing timeout and starting - * the timeout from scratch. + * Sets the amount of time after which, if the request does not return any data within the timeout period, + * the request/response is closed and the related futures are failed with a {@link java.util.concurrent.TimeoutException}, + * e.g. {@code Future} or {@code Future} response body. * - * @param timeoutMs The quantity of time in milliseconds. + * @param timeout the amount of time in milliseconds. * @return a reference to this, so the API can be used fluently */ @Fluent - HttpClientRequest setTimeout(long timeoutMs); + HttpClientRequest idleTimeout(long timeout); /** * Set a push handler for this request.

@@ -483,8 +493,7 @@ default boolean reset() { * @param payload the frame payload * @return a reference to this, so the API can be used fluently */ - @Fluent - HttpClientRequest writeCustomFrame(int type, int flags, Buffer payload); + Future writeCustomFrame(int type, int flags, Buffer payload); /** * @return the id of the stream of this response, {@literal -1} when it is not yet determined, i.e @@ -499,8 +508,7 @@ default int streamId() { * * @param frame the frame to write */ - @Fluent - default HttpClientRequest writeCustomFrame(HttpFrame frame) { + default Future writeCustomFrame(HttpFrame frame) { return writeCustomFrame(frame.type(), frame.flags(), frame.payload()); } diff --git a/src/main/java/io/vertx/core/http/HttpHeaders.java b/src/main/java/io/vertx/core/http/HttpHeaders.java index 0e8e7fc5e2d..2b603af4feb 100644 --- a/src/main/java/io/vertx/core/http/HttpHeaders.java +++ b/src/main/java/io/vertx/core/http/HttpHeaders.java @@ -13,6 +13,7 @@ import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderValues; +import io.netty.handler.codec.http2.Http2Headers; import io.netty.util.AsciiString; import io.vertx.codegen.annotations.GenIgnore; import io.vertx.codegen.annotations.VertxGen; @@ -427,6 +428,36 @@ public interface HttpHeaders { @GenIgnore(GenIgnore.PERMITTED_TYPE) CharSequence VARY = createOptimized("vary"); + /** + * HTTP/2 {@code :path} pseudo header + */ + @GenIgnore(GenIgnore.PERMITTED_TYPE) + CharSequence PSEUDO_PATH = Http2Headers.PseudoHeaderName.PATH.value(); + + /** + * HTTP/2 {@code :authority} pseudo header + */ + @GenIgnore(GenIgnore.PERMITTED_TYPE) + CharSequence PSEUDO_AUTHORITY = Http2Headers.PseudoHeaderName.AUTHORITY.value(); + + /** + * HTTP/2 {@code :scheme} pseudo header + */ + @GenIgnore(GenIgnore.PERMITTED_TYPE) + CharSequence PSEUDO_SCHEME = Http2Headers.PseudoHeaderName.SCHEME.value(); + + /** + * HTTP/2 {@code :status} pseudo header + */ + @GenIgnore(GenIgnore.PERMITTED_TYPE) + CharSequence PSEUDO_STATUS = Http2Headers.PseudoHeaderName.STATUS.value(); + + /** + * HTTP/2 {@code :method} pseudo hedaer + */ + @GenIgnore(GenIgnore.PERMITTED_TYPE) + CharSequence PSEUDO_METHOD = Http2Headers.PseudoHeaderName.METHOD.value(); + /** * Create an optimized {@link CharSequence} which can be used as header name or value. * This should be used if you expect to use it multiple times liked for example adding the same header name or value diff --git a/src/main/java/io/vertx/core/http/HttpServer.java b/src/main/java/io/vertx/core/http/HttpServer.java index 0f518079ee6..1c7ab54f04c 100644 --- a/src/main/java/io/vertx/core/http/HttpServer.java +++ b/src/main/java/io/vertx/core/http/HttpServer.java @@ -17,8 +17,9 @@ import io.vertx.codegen.annotations.Fluent; import io.vertx.codegen.annotations.VertxGen; import io.vertx.core.metrics.Measured; -import io.vertx.core.net.SSLOptions; +import io.vertx.core.net.ServerSSLOptions; import io.vertx.core.net.SocketAddress; +import io.vertx.core.net.TrafficShapingOptions; import io.vertx.core.net.impl.SocketAddressImpl; /** @@ -100,14 +101,41 @@ public interface HttpServer extends Measured { Handler webSocketHandler(); /** - * Update the server SSL options. + * Update the server with new SSL {@code options}, the update happens if the options object is valid and different + * from the existing options object. * - * Update only happens if the SSL options is valid. + *

The boolean succeeded future result indicates whether the update occurred. * * @param options the new SSL options * @return a future signaling the update success */ - Future updateSSLOptions(SSLOptions options); + default Future updateSSLOptions(ServerSSLOptions options) { + return updateSSLOptions(options, false); + } + + /** + *

Update the server with new SSL {@code options}, the update happens if the options object is valid and different + * from the existing options object. + * + *

The {@code options} object is compared using its {@code equals} method against the existing options to prevent + * an update when the objects are equals since loading options can be costly, this can happen for share TCP servers. + * When object are equals, setting {@code force} to {@code true} forces the update. + * + *

The boolean succeeded future result indicates whether the update occurred. + * + * @param options the new SSL options + * @param force force the update when options are equals + * @return a future signaling the update success + */ + Future updateSSLOptions(ServerSSLOptions options, boolean force); + + /** + * Update traffic shaping options {@code options}, the update happens if valid values are passed for traffic + * shaping options. This update happens synchronously and at best effort for rate update to take effect immediately. + * + * @param options the new traffic shaping options + */ + void updateTrafficShapingOptions(TrafficShapingOptions options); /** * Tell the server to start listening. The server will listen on the port and host specified in the diff --git a/src/main/java/io/vertx/core/http/HttpServerOptions.java b/src/main/java/io/vertx/core/http/HttpServerOptions.java index c13b8d80ff4..3de40d5ae53 100755 --- a/src/main/java/io/vertx/core/http/HttpServerOptions.java +++ b/src/main/java/io/vertx/core/http/HttpServerOptions.java @@ -15,6 +15,7 @@ import io.netty.handler.logging.ByteBufFormat; import io.vertx.codegen.annotations.DataObject; import io.vertx.codegen.annotations.Unstable; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.Handler; import io.vertx.core.buffer.Buffer; import io.vertx.core.impl.Arguments; @@ -45,7 +46,8 @@ * * @author Tim Fox */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public class HttpServerOptions extends NetServerOptions { /** @@ -104,6 +106,11 @@ public class HttpServerOptions extends NetServerOptions { */ public static final List DEFAULT_ALPN_VERSIONS = Collections.unmodifiableList(Arrays.asList(HttpVersion.HTTP_2, HttpVersion.HTTP_1_1)); + /** + * Default H2C is enabled = {@code true} + */ + public static final boolean DEFAULT_HTTP2_CLEAR_TEXT_ENABLED = true; + /** * The default initial settings max concurrent stream for an HTTP/2 server = 100 */ @@ -169,6 +176,21 @@ public class HttpServerOptions extends NetServerOptions { */ public static final boolean DEFAULT_REGISTER_WEBSOCKET_WRITE_HANDLERS = false; + /** + * HTTP/2 RST floods DDOS protection, max number of RST frame per time window allowed = 200. + */ + public static final int DEFAULT_HTTP2_RST_FLOOD_MAX_RST_FRAME_PER_WINDOW = 200; + + /** + * HTTP/2 RST floods DDOS protection, time window duration = 30. + */ + public static final int DEFAULT_HTTP2_RST_FLOOD_WINDOW_DURATION = 30; + + /** + * HTTP/2 RST floods DDOS protection, time window duration unit = {@link TimeUnit#SECONDS}. + */ + public static final TimeUnit DEFAULT_HTTP2_RST_FLOOD_WINDOW_DURATION_TIME_UNIT = TimeUnit.SECONDS; + private boolean compressionSupported; private int compressionLevel; private List compressors; @@ -182,6 +204,7 @@ public class HttpServerOptions extends NetServerOptions { private int maxFormAttributeSize; private Http2Settings initialSettings; private List alpnVersions; + private boolean http2ClearTextEnabled; private int http2ConnectionWindowSize; private boolean decompressionSupported; private boolean acceptUnmaskedFrames; @@ -194,6 +217,9 @@ public class HttpServerOptions extends NetServerOptions { private int webSocketClosingTimeout; private TracingPolicy tracingPolicy; private boolean registerWebSocketWriteHandlers; + private int http2RstFloodMaxRstFramePerWindow; + private int http2RstFloodWindowDuration; + private TimeUnit http2RstFloodWindowDurationTimeUnit; /** * Default constructor @@ -224,6 +250,7 @@ public HttpServerOptions(HttpServerOptions other) { this.maxFormAttributeSize = other.getMaxFormAttributeSize(); this.initialSettings = other.initialSettings != null ? new Http2Settings(other.initialSettings) : null; this.alpnVersions = other.alpnVersions != null ? new ArrayList<>(other.alpnVersions) : null; + this.http2ClearTextEnabled = other.http2ClearTextEnabled; this.http2ConnectionWindowSize = other.http2ConnectionWindowSize; this.decompressionSupported = other.isDecompressionSupported(); this.acceptUnmaskedFrames = other.isAcceptUnmaskedFrames(); @@ -236,6 +263,9 @@ public HttpServerOptions(HttpServerOptions other) { this.webSocketClosingTimeout = other.webSocketClosingTimeout; this.tracingPolicy = other.tracingPolicy; this.registerWebSocketWriteHandlers = other.registerWebSocketWriteHandlers; + this.http2RstFloodMaxRstFramePerWindow = other.http2RstFloodMaxRstFramePerWindow; + this.http2RstFloodWindowDuration = other.http2RstFloodWindowDuration; + this.http2RstFloodWindowDurationTimeUnit = other.http2RstFloodWindowDurationTimeUnit; } /** @@ -273,6 +303,7 @@ private void init() { maxFormAttributeSize = DEFAULT_MAX_FORM_ATTRIBUTE_SIZE; initialSettings = new Http2Settings().setMaxConcurrentStreams(DEFAULT_INITIAL_SETTINGS_MAX_CONCURRENT_STREAMS); alpnVersions = new ArrayList<>(DEFAULT_ALPN_VERSIONS); + http2ClearTextEnabled = DEFAULT_HTTP2_CLEAR_TEXT_ENABLED; http2ConnectionWindowSize = DEFAULT_HTTP2_CONNECTION_WINDOW_SIZE; decompressionSupported = DEFAULT_DECOMPRESSION_SUPPORTED; acceptUnmaskedFrames = DEFAULT_ACCEPT_UNMASKED_FRAMES; @@ -285,6 +316,18 @@ private void init() { webSocketClosingTimeout = DEFAULT_WEBSOCKET_CLOSING_TIMEOUT; tracingPolicy = DEFAULT_TRACING_POLICY; registerWebSocketWriteHandlers = DEFAULT_REGISTER_WEBSOCKET_WRITE_HANDLERS; + http2RstFloodMaxRstFramePerWindow = DEFAULT_HTTP2_RST_FLOOD_MAX_RST_FRAME_PER_WINDOW; + http2RstFloodWindowDuration = DEFAULT_HTTP2_RST_FLOOD_WINDOW_DURATION; + http2RstFloodWindowDurationTimeUnit = DEFAULT_HTTP2_RST_FLOOD_WINDOW_DURATION_TIME_UNIT; + } + + /** + * Copy these options. + * + * @return a copy of this + */ + public HttpServerOptions copy() { + return new HttpServerOptions(this); } @Override @@ -377,44 +420,12 @@ public HttpServerOptions setKeyCertOptions(KeyCertOptions options) { return this; } - @Override - public HttpServerOptions setKeyStoreOptions(JksOptions options) { - super.setKeyStoreOptions(options); - return this; - } - - @Override - public HttpServerOptions setPfxKeyCertOptions(PfxOptions options) { - return (HttpServerOptions) super.setPfxKeyCertOptions(options); - } - - @Override - public HttpServerOptions setPemKeyCertOptions(PemKeyCertOptions options) { - return (HttpServerOptions) super.setPemKeyCertOptions(options); - } - @Override public HttpServerOptions setTrustOptions(TrustOptions options) { super.setTrustOptions(options); return this; } - @Override - public HttpServerOptions setTrustStoreOptions(JksOptions options) { - super.setTrustStoreOptions(options); - return this; - } - - @Override - public HttpServerOptions setPemTrustOptions(PemTrustOptions options) { - return (HttpServerOptions) super.setPemTrustOptions(options); - } - - @Override - public HttpServerOptions setPfxTrustOptions(PfxOptions options) { - return (HttpServerOptions) super.setPfxTrustOptions(options); - } - @Override public HttpServerOptions addEnabledCipherSuite(String suite) { super.addEnabledCipherSuite(suite); @@ -492,16 +503,6 @@ public HttpServerOptions setSslEngineOptions(SSLEngineOptions sslEngineOptions) return this; } - @Override - public HttpServerOptions setJdkSslEngineOptions(JdkSSLEngineOptions sslEngineOptions) { - return (HttpServerOptions) super.setSslEngineOptions(sslEngineOptions); - } - - @Override - public HttpServerOptions setOpenSslEngineOptions(OpenSSLEngineOptions sslEngineOptions) { - return (HttpServerOptions) super.setSslEngineOptions(sslEngineOptions); - } - @Override public HttpServerOptions setEnabledSecureTransportProtocols(Set enabledSecureTransportProtocols) { return (HttpServerOptions) super.setEnabledSecureTransportProtocols(enabledSecureTransportProtocols); @@ -824,6 +825,24 @@ public HttpServerOptions setAlpnVersions(List alpnVersions) { return this; } + /** + * @return whether the server accepts HTTP/2 over clear text connections + */ + public boolean isHttp2ClearTextEnabled() { + return http2ClearTextEnabled; + } + + /** + * Set whether HTTP/2 over clear text is enabled or disabled, default is enabled. + * + * @param http2ClearTextEnabled whether to accept HTTP/2 over clear text + * @return a reference to this, so the API can be used fluently + */ + public HttpServerOptions setHttp2ClearTextEnabled(boolean http2ClearTextEnabled) { + this.http2ClearTextEnabled = http2ClearTextEnabled; + return this; + } + /** * @return the default HTTP/2 connection window size */ @@ -1096,4 +1115,65 @@ public HttpServerOptions setRegisterWebSocketWriteHandlers(boolean registerWebSo this.registerWebSocketWriteHandlers = registerWebSocketWriteHandlers; return this; } + + /** + * @return the max number of RST frame allowed per time window + */ + public int getHttp2RstFloodMaxRstFramePerWindow() { + return http2RstFloodMaxRstFramePerWindow; + } + + /** + * Set the max number of RST frame allowed per time window, this is used to prevent HTTP/2 RST frame flood DDOS + * attacks. The default value is {@link #DEFAULT_HTTP2_RST_FLOOD_MAX_RST_FRAME_PER_WINDOW}, setting zero or a negative value, disables flood protection. + * + * @param http2RstFloodMaxRstFramePerWindow the new maximum + * @return a reference to this, so the API can be used fluently + */ + public HttpServerOptions setHttp2RstFloodMaxRstFramePerWindow(int http2RstFloodMaxRstFramePerWindow) { + this.http2RstFloodMaxRstFramePerWindow = http2RstFloodMaxRstFramePerWindow; + return this; + } + + /** + * @return the duration of the time window when checking the max number of RST frames. + */ + public int getHttp2RstFloodWindowDuration() { + return http2RstFloodWindowDuration; + } + + /** + * Set the duration of the time window when checking the max number of RST frames, this is used to prevent HTTP/2 RST frame flood DDOS + * attacks. The default value is {@link #DEFAULT_HTTP2_RST_FLOOD_WINDOW_DURATION}, setting zero or a negative value, disables flood protection. + * + * @param http2RstFloodWindowDuration the new duration + * @return a reference to this, so the API can be used fluently + */ + public HttpServerOptions setHttp2RstFloodWindowDuration(int http2RstFloodWindowDuration) { + this.http2RstFloodWindowDuration = http2RstFloodWindowDuration; + return this; + } + + /** + * @return the time unit of the duration of the time window when checking the max number of RST frames. + */ + public TimeUnit getHttp2RstFloodWindowDurationTimeUnit() { + return http2RstFloodWindowDurationTimeUnit; + } + + /** + * Set the time unit of the duration of the time window when checking the max number of RST frames, this is used to + * prevent HTTP/2 RST frame flood DDOS attacks. The default value is {@link #DEFAULT_HTTP2_RST_FLOOD_WINDOW_DURATION_TIME_UNIT}, + * setting zero or a negative value, disables the flood protection. + * + * @param http2RstFloodWindowDurationTimeUnit the new duration + * @return a reference to this, so the API can be used fluently + */ + public HttpServerOptions setHttp2RstFloodWindowDurationTimeUnit(TimeUnit http2RstFloodWindowDurationTimeUnit) { + if (http2RstFloodWindowDurationTimeUnit == null) { + throw new NullPointerException(); + } + this.http2RstFloodWindowDurationTimeUnit = http2RstFloodWindowDurationTimeUnit; + return this; + } } diff --git a/src/main/java/io/vertx/core/http/HttpServerRequest.java b/src/main/java/io/vertx/core/http/HttpServerRequest.java index ba636869298..f0084d37b7a 100644 --- a/src/main/java/io/vertx/core/http/HttpServerRequest.java +++ b/src/main/java/io/vertx/core/http/HttpServerRequest.java @@ -20,6 +20,7 @@ import io.vertx.core.Handler; import io.vertx.core.MultiMap; import io.vertx.core.buffer.Buffer; +import io.vertx.core.net.HostAndPort; import io.vertx.core.net.NetSocket; import io.vertx.core.net.SocketAddress; import io.vertx.core.streams.ReadStream; @@ -73,7 +74,7 @@ public interface HttpServerRequest extends ReadStream { } HttpServerResponse response = request.response(); response.setStatusCode(status.code()).end(); - response.close(); + request.connection().close(); }; @Override @@ -135,10 +136,13 @@ default boolean isSSL() { String query(); /** - * @return the request host. For HTTP2 it returns the {@literal :authority} pseudo header otherwise it returns the {@literal Host} header + * @return the request authority. For HTTP/2 the {@literal :authority} pseudo header is returned, for HTTP/1.x the + * {@literal Host} header is returned or {@code null} when no such header is present. When the authority + * string does not carry a port, the {@link HostAndPort#port()} returns {@code -1} to indicate the + * scheme port is prevalent. */ @Nullable - String host(); + HostAndPort authority(); /** * @return the total number of bytes read for the body of the request. diff --git a/src/main/java/io/vertx/core/http/HttpServerResponse.java b/src/main/java/io/vertx/core/http/HttpServerResponse.java index 0d7ab37c0c0..48d26e8c64c 100644 --- a/src/main/java/io/vertx/core/http/HttpServerResponse.java +++ b/src/main/java/io/vertx/core/http/HttpServerResponse.java @@ -12,11 +12,11 @@ package io.vertx.core.http; import io.vertx.codegen.annotations.*; -import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.MultiMap; import io.vertx.core.buffer.Buffer; +import io.vertx.core.net.HostAndPort; import io.vertx.core.streams.ReadStream; import io.vertx.core.streams.WriteStream; @@ -228,8 +228,7 @@ public interface HttpServerResponse extends WriteStream { * Must only be used if the request contains an "Expect:100-Continue" header * @return a reference to this, so the API can be used fluently */ - @Fluent - HttpServerResponse writeContinue(); + Future writeContinue(); /** * Used to write an interim 103 Early Hints response to return some HTTP headers before the final HTTP message. @@ -358,11 +357,6 @@ default Future sendFile(String filename, long offset) { */ Future sendFile(String filename, long offset, long length); - /** - * Close the underlying TCP connection corresponding to the request. - */ - void close(); - /** * @return has the response already ended? */ @@ -421,7 +415,7 @@ default Future push(HttpMethod method, String host, String p * Like {@link #push(HttpMethod, String, String, MultiMap)} with the host copied from the current request. */ default Future push(HttpMethod method, String path, MultiMap headers) { - return push(method, null, path, headers); + return push(method, (HostAndPort) null, path, headers); } /** @@ -431,6 +425,25 @@ default Future push(HttpMethod method, String path) { return push(method, null, path); } + /** + * Push a response to the client.

+ * + * The {@code handler} will be notified with a success when the push can be sent and with + * a failure when the client has disabled push or reset the push before it has been sent.

+ * + * The {@code handler} may be queued if the client has reduced the maximum number of streams the server can push + * concurrently.

+ * + * Push can be sent only for peer initiated streams and if the response is not ended. + * + * @param method the method of the promised request + * @param authority the authority of the promised request + * @param path the path of the promised request + * @param headers the headers of the promised request + * @return a future notified when the response can be written + */ + Future push(HttpMethod method, HostAndPort authority, String path, MultiMap headers); + /** * Push a response to the client.

* @@ -447,7 +460,9 @@ default Future push(HttpMethod method, String path) { * @param path the path of the promised request * @param headers the headers of the promised request * @return a future notified when the response can be written + * @deprecated instead use {@link #push(HttpMethod, HostAndPort, String, MultiMap)} */ + @Deprecated Future push(HttpMethod method, String host, String path, MultiMap headers); /** @@ -482,16 +497,14 @@ default boolean reset() { * @param payload the frame payload * @return a reference to this, so the API can be used fluently */ - @Fluent - HttpServerResponse writeCustomFrame(int type, int flags, Buffer payload); + Future writeCustomFrame(int type, int flags, Buffer payload); /** * Like {@link #writeCustomFrame(int, int, Buffer)} but with an {@link HttpFrame}. * * @param frame the frame to write */ - @Fluent - default HttpServerResponse writeCustomFrame(HttpFrame frame) { + default Future writeCustomFrame(HttpFrame frame) { return writeCustomFrame(frame.type(), frame.flags(), frame.payload()); } diff --git a/src/main/java/io/vertx/core/http/PoolOptions.java b/src/main/java/io/vertx/core/http/PoolOptions.java new file mode 100644 index 00000000000..c1608c214a4 --- /dev/null +++ b/src/main/java/io/vertx/core/http/PoolOptions.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.http; + +import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; +import io.vertx.core.impl.Arguments; +import io.vertx.core.json.JsonObject; + +/** + * Options configuring a {@link HttpClient} pool. + * + * @author Julien Viet + */ +@DataObject +@JsonGen(publicConverter = false) +public class PoolOptions { + + /** + * The default maximum number of HTTP/1 connections a client will pool = 5 + */ + public static final int DEFAULT_MAX_POOL_SIZE = 5; + + /** + * The default maximum number of connections an HTTP/2 client will pool = 1 + */ + public static final int DEFAULT_HTTP2_MAX_POOL_SIZE = 1; + + /** + * Default max wait queue size = -1 (unbounded) + */ + public static final int DEFAULT_MAX_WAIT_QUEUE_SIZE = -1; + + /** + * Default pool cleaner period = 1000 ms (1 second) + */ + public static final int DEFAULT_POOL_CLEANER_PERIOD = 1000; + + /** + * Default pool event loop size = 0 (reuse current event-loop) + */ + public static final int DEFAULT_POOL_EVENT_LOOP_SIZE = 0; + + private int http1MaxSize; + private int http2MaxSize; + private int cleanerPeriod; + private int eventLoopSize; + private int maxWaitQueueSize; + + /** + * Default constructor + */ + public PoolOptions() { + http1MaxSize = DEFAULT_MAX_POOL_SIZE; + http2MaxSize = DEFAULT_HTTP2_MAX_POOL_SIZE; + cleanerPeriod = DEFAULT_POOL_CLEANER_PERIOD; + eventLoopSize = DEFAULT_POOL_EVENT_LOOP_SIZE; + maxWaitQueueSize = DEFAULT_MAX_WAIT_QUEUE_SIZE; + } + + /** + * Copy constructor + * + * @param other the options to copy + */ + public PoolOptions(PoolOptions other) { + this.http1MaxSize = other.http1MaxSize; + this.http2MaxSize = other.http2MaxSize; + this.cleanerPeriod = other.cleanerPeriod; + this.eventLoopSize = other.eventLoopSize; + this.maxWaitQueueSize = other.maxWaitQueueSize; + } + + /** + * Constructor to create an options from JSON + * + * @param json the JSON + */ + public PoolOptions(JsonObject json) { + PoolOptionsConverter.fromJson(json, this); + } + + /** + * Get the maximum pool size for HTTP/1.x connections + * + * @return the maximum pool size + */ + public int getHttp1MaxSize() { + return http1MaxSize; + } + + /** + * Set the maximum pool size for HTTP/1.x connections + * + * @param http1MaxSize the maximum pool size + * @return a reference to this, so the API can be used fluently + */ + public PoolOptions setHttp1MaxSize(int http1MaxSize) { + if (http1MaxSize < 1) { + throw new IllegalArgumentException("maxPoolSize must be > 0"); + } + this.http1MaxSize = http1MaxSize; + return this; + } + + /** + * Get the maximum pool size for HTTP/2 connections + * + * @return the maximum pool size + */ + public int getHttp2MaxSize() { + return http2MaxSize; + } + + /** + * Set the maximum pool size for HTTP/2 connections + * + * @param max the maximum pool size + * @return a reference to this, so the API can be used fluently + */ + public PoolOptions setHttp2MaxSize(int max) { + if (max < 1) { + throw new IllegalArgumentException("http2MaxPoolSize must be > 0"); + } + this.http2MaxSize = max; + return this; + } + + /** + * @return the connection pool cleaner period in ms. + */ + public int getCleanerPeriod() { + return cleanerPeriod; + } + + /** + * Set the connection pool cleaner period in milli seconds, a non positive value disables expiration checks and connections + * will remain in the pool until they are closed. + * + * @param cleanerPeriod the pool cleaner period + * @return a reference to this, so the API can be used fluently + */ + public PoolOptions setCleanerPeriod(int cleanerPeriod) { + this.cleanerPeriod = cleanerPeriod; + return this; + } + + /** + * @return the max number of event-loop a pool will use, the default value is {@code 0} which implies + * to reuse the current event-loop + */ + public int getEventLoopSize() { + return eventLoopSize; + } + + /** + * Set the number of event-loop the pool use. + * + *

    + *
  • when the size is {@code 0}, the client pool will use the current event-loop
  • + *
  • otherwise the client will create and use its own event loop
  • + *
+ * + * The default size is {@code 0}. + * + * @param eventLoopSize the new size + * @return a reference to this, so the API can be used fluently + */ + public PoolOptions setEventLoopSize(int eventLoopSize) { + Arguments.require(eventLoopSize >= 0, "poolEventLoopSize must be >= 0"); + this.eventLoopSize = eventLoopSize; + return this; + } + + /** + * Set the maximum requests allowed in the wait queue, any requests beyond the max size will result in + * a ConnectionPoolTooBusyException. If the value is set to a negative number then the queue will be unbounded. + * @param maxWaitQueueSize the maximum number of waiting requests + * @return a reference to this, so the API can be used fluently + */ + public PoolOptions setMaxWaitQueueSize(int maxWaitQueueSize) { + this.maxWaitQueueSize = maxWaitQueueSize; + return this; + } + + /** + * Returns the maximum wait queue size + * @return the maximum wait queue size + */ + public int getMaxWaitQueueSize() { + return maxWaitQueueSize; + } + + public JsonObject toJson() { + JsonObject json = new JsonObject(); + PoolOptionsConverter.toJson(this, json); + return json; + } +} diff --git a/src/main/java/io/vertx/core/http/RequestOptions.java b/src/main/java/io/vertx/core/http/RequestOptions.java index fafc4f028bb..a7de893d99b 100644 --- a/src/main/java/io/vertx/core/http/RequestOptions.java +++ b/src/main/java/io/vertx/core/http/RequestOptions.java @@ -13,11 +13,11 @@ import io.vertx.codegen.annotations.DataObject; import io.vertx.codegen.annotations.GenIgnore; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.MultiMap; import io.vertx.core.VertxException; import io.vertx.core.json.JsonObject; -import io.vertx.core.net.ProxyOptions; -import io.vertx.core.net.SocketAddress; +import io.vertx.core.net.*; import java.net.MalformedURLException; import java.net.URL; @@ -32,7 +32,8 @@ * * @author Julien Viet */ -@DataObject(generateConverter = true) +@DataObject +@JsonGen(publicConverter = false) public class RequestOptions { /** @@ -76,20 +77,33 @@ public class RequestOptions { public static final boolean DEFAULT_FOLLOW_REDIRECTS = false; /** - * The default request timeout = {@code 0} (disabled) + * The default request timeout = {@code -1L} (disabled) */ - public static final long DEFAULT_TIMEOUT = 0; + public static final long DEFAULT_TIMEOUT = -1L; + + /** + * The default connect timeout = {@code -1L} (disabled) + */ + public static final long DEFAULT_CONNECT_TIMEOUT = -1L; + + /** + * The default idle timeout = {@code -1L} (disabled) + */ + public static final long DEFAULT_IDLE_TIMEOUT = -1L; private ProxyOptions proxyOptions; - private SocketAddress server; + private Address server; private HttpMethod method; private String host; private Integer port; private Boolean ssl; + private ClientSSLOptions sslOptions;; private String uri; private MultiMap headers; private boolean followRedirects; private long timeout; + private long connectTimeout; + private long idleTimeout; private String traceOperation; /** @@ -102,9 +116,12 @@ public RequestOptions() { host = DEFAULT_HOST; port = DEFAULT_PORT; ssl = DEFAULT_SSL; + sslOptions = null; uri = DEFAULT_URI; followRedirects = DEFAULT_FOLLOW_REDIRECTS; timeout = DEFAULT_TIMEOUT; + connectTimeout = DEFAULT_CONNECT_TIMEOUT; + idleTimeout = DEFAULT_IDLE_TIMEOUT; traceOperation = null; } @@ -120,8 +137,11 @@ public RequestOptions(RequestOptions other) { setHost(other.host); setPort(other.port); setSsl(other.ssl); + sslOptions = other.sslOptions != null ? new ClientSSLOptions(other.sslOptions) : null; setURI(other.uri); setFollowRedirects(other.followRedirects); + setIdleTimeout(other.idleTimeout); + setConnectTimeout(other.connectTimeout); setTimeout(other.timeout); if (other.headers != null) { setHeaders(MultiMap.caseInsensitiveMultiMap().setAll(other.headers)); @@ -143,14 +163,7 @@ public RequestOptions(JsonObject json) { } JsonObject server = json.getJsonObject("server"); if (server != null) { - Integer port = server.getInteger("port", 80); - String host = server.getString("host"); - String path = server.getString("path"); - if (host != null) { - this.server = SocketAddress.inetSocketAddress(port, host); - } else if (path != null) { - this.server = SocketAddress.domainSocketAddress(path); - } + this.server = SocketAddress.fromJson(server); } JsonObject headers = json.getJsonObject("headers"); if (headers != null) { @@ -195,7 +208,7 @@ public RequestOptions setProxyOptions(ProxyOptions proxyOptions) { * * @return the server address */ - public SocketAddress getServer() { + public Address getServer() { return server; } @@ -209,7 +222,7 @@ public SocketAddress getServer() { * * @return a reference to this, so the API can be used fluently */ - public RequestOptions setServer(SocketAddress server) { + public RequestOptions setServer(Address server) { this.server = server; return this; } @@ -291,6 +304,25 @@ public RequestOptions setSsl(Boolean ssl) { return this; } + /** + * @return the SSL options + */ + public ClientSSLOptions getSslOptions() { + return sslOptions; + } + + /** + * Set the SSL options to use. + *

+ * When none is provided, the client SSL options will be used instead. + * @param sslOptions the SSL options to use + * @return a reference to this, so the API can be used fluently + */ + public RequestOptions setSslOptions(ClientSSLOptions sslOptions) { + this.sslOptions = sslOptions; + return this; + } + /** * @return the request relative URI */ @@ -328,27 +360,78 @@ public RequestOptions setFollowRedirects(Boolean followRedirects) { } /** - * @return the amount of time after which if the request does not return any data within the timeout period an - * {@link java.util.concurrent.TimeoutException} will be passed to the exception handler and - * the request will be closed. + * @see #setTimeout(long) */ public long getTimeout() { return timeout; } /** - * Sets the amount of time after which if the request does not return any data within the timeout period an - * {@link java.util.concurrent.TimeoutException} will be passed to the exception handler and - * the request will be closed. + * Sets both connect and idle timeouts for the request * - * @param timeout the amount of time in milliseconds. - * @return a reference to this, so the API can be used fluently + *

    + *
  • connect timeout: if the request is not obtained from the client within the timeout period, the {@code Future} + * obtained from the client is failed with a {@link java.util.concurrent.TimeoutException}.
  • + *
  • idle timeout: if the request does not return any data within the timeout period, the request/response is closed and the + * related futures are failed with a {@link java.util.concurrent.TimeoutException}, e.g. {@code Future} + * or {@code Future} response body.
  • + *
+ * + * The connect and idle timeouts can be set separately using {@link #setConnectTimeout(long)} and {@link #setIdleTimeout(long)} */ public RequestOptions setTimeout(long timeout) { this.timeout = timeout; return this; } + /** + * @return the amount of time after which, if the request is not obtained from the client within the timeout period, + * the {@code Future} obtained from the client is failed with a {@link java.util.concurrent.TimeoutException} + */ + public long getConnectTimeout() { + return connectTimeout; + } + + /** + * Sets the amount of time after which, if the request is not obtained from the client within the timeout period, + * the {@code Future} obtained from the client is failed with a {@link java.util.concurrent.TimeoutException}. + * + * Note this is not related to the TCP {@link HttpClientOptions#setConnectTimeout(int)} option, when a request is made against + * a pooled HTTP client, the timeout applies to the duration to obtain a connection from the pool to serve the request, the timeout + * might fire because the server does not respond in time or the pool is too busy to serve a request. + * + * @param timeout the amount of time in milliseconds. + * @return a reference to this, so the API can be used fluently + */ + public RequestOptions setConnectTimeout(long timeout) { + this.connectTimeout = timeout; + return this; + } + + /** + * @return the amount of time after which, if the request does not return any data within the timeout period, + * the request/response is closed and the related futures are failed with a {@link java.util.concurrent.TimeoutException} + */ + public long getIdleTimeout() { + return idleTimeout; + } + + /** + * Sets the amount of time after which, if the request does not return any data within the timeout period, + * the request/response is closed and the related futures are failed with a {@link java.util.concurrent.TimeoutException}, + * e.g. {@code Future} or {@code Future} response body. + * + *

The timeout starts after a connection is obtained from the client, similar to calling + * {@link HttpClientRequest#idleTimeout(long)}. + * + * @param timeout the amount of time in milliseconds. + * @return a reference to this, so the API can be used fluently + */ + public RequestOptions setIdleTimeout(long timeout) { + this.idleTimeout = timeout; + return this; + } + private URL parseUrl(String surl) { // Note - parsing a URL this way is slower than specifying host, port and relativeURI try { @@ -555,15 +638,10 @@ public JsonObject toJson() { if (method != null) { json.put("method", method.name()); } - if (this.server != null) { - JsonObject server = new JsonObject(); - if (this.server.isInetSocket()) { - server.put("host", this.server.host()); - server.put("port", this.server.port()); - } else { - server.put("path", this.server.path()); - } - json.put("server", server); + Address serverAddr = this.server; + if (serverAddr instanceof SocketAddress) { + SocketAddress socketAddr = (SocketAddress) serverAddr; + json.put("server", socketAddr.toJson()); } if (this.headers != null) { JsonObject headers = new JsonObject(); diff --git a/src/main/java/io/vertx/core/http/ServerWebSocket.java b/src/main/java/io/vertx/core/http/ServerWebSocket.java index 51a1126780d..46f76ae1194 100644 --- a/src/main/java/io/vertx/core/http/ServerWebSocket.java +++ b/src/main/java/io/vertx/core/http/ServerWebSocket.java @@ -18,6 +18,7 @@ import io.vertx.core.Handler; import io.vertx.core.Promise; import io.vertx.core.buffer.Buffer; +import io.vertx.core.net.HostAndPort; import javax.net.ssl.SSLSession; @@ -30,7 +31,7 @@ * @author Tim Fox */ @VertxGen -public interface ServerWebSocket extends WebSocketBase { +public interface ServerWebSocket extends WebSocket { @Override ServerWebSocket exceptionHandler(Handler handler); @@ -69,10 +70,10 @@ public interface ServerWebSocket extends WebSocketBase { String scheme(); /** - * @return the WebSocket handshake host + * @return the WebSocket handshake authority */ @Nullable - String host(); + HostAndPort authority(); /* * @return the WebSocket handshake URI. This is a relative URI. diff --git a/src/main/java/io/vertx/core/http/WebSocket.java b/src/main/java/io/vertx/core/http/WebSocket.java index 34db572837e..aec8bef31ad 100644 --- a/src/main/java/io/vertx/core/http/WebSocket.java +++ b/src/main/java/io/vertx/core/http/WebSocket.java @@ -11,12 +11,18 @@ package io.vertx.core.http; +import io.vertx.codegen.annotations.Nullable; import io.vertx.codegen.annotations.VertxGen; import io.vertx.core.Handler; import io.vertx.core.buffer.Buffer; +import io.vertx.core.streams.ReadStream; +import io.vertx.core.streams.WriteStream; /** - * Represents a client-side WebSocket. + * Common WebSocket implementation. + *

+ * It implements both {@link ReadStream} and {@link WriteStream} so it can be used with + * {@link io.vertx.core.streams.Pipe} to pipe data with flow control. * * @author Tim Fox */ @@ -52,4 +58,14 @@ public interface WebSocket extends WebSocketBase { @Override WebSocket frameHandler(Handler handler); + + @Override + WebSocket textMessageHandler(@Nullable Handler handler); + + @Override + WebSocket binaryMessageHandler(@Nullable Handler handler); + + @Override + WebSocket pongHandler(@Nullable Handler handler); + } diff --git a/src/main/java/io/vertx/core/http/WebSocketClient.java b/src/main/java/io/vertx/core/http/WebSocketClient.java new file mode 100644 index 00000000000..ca1ade172dd --- /dev/null +++ b/src/main/java/io/vertx/core/http/WebSocketClient.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.http; + +import io.vertx.codegen.annotations.VertxGen; +import io.vertx.core.Future; +import io.vertx.core.metrics.Measured; +import io.vertx.core.net.ClientSSLOptions; + +import java.util.concurrent.TimeUnit; + +/** + * An asynchronous WebSocket client. + *

+ * It allows you to open WebSockets to servers. + *

+ * + * @author Tim Fox + */ +@VertxGen +public interface WebSocketClient extends Measured { + + /** + * Create a WebSocket that is not yet connected to the server. + * + * @return the client WebSocket + */ + ClientWebSocket webSocket(); + + /** + * Connect a WebSocket to the specified port, host and relative request URI. + * + * @param port the port + * @param host the host + * @param requestURI the relative URI + * @return a future notified when the WebSocket when connected + */ + default Future connect(int port, String host, String requestURI) { + return connect(new WebSocketConnectOptions().setURI(requestURI).setHost(host).setPort(port)); + } + + /** + * Connect a WebSocket to the default client port and specified host and relative request URI. + * + * @param host the host + * @param requestURI the relative URI + * @return a future notified when the WebSocket when connected + */ + default Future connect(String host, String requestURI) { + return connect(new WebSocketConnectOptions().setURI(requestURI).setHost(host)); + } + + /** + * Connect a WebSocket to the default client port, default client host and specified, relative request URI. + * + * @param requestURI the relative URI + * @return a future notified when the WebSocket when connected + */ + default Future connect(String requestURI) { + return connect(new WebSocketConnectOptions().setURI(requestURI)); + } + + /** + * Connect a WebSocket with the specified options. + * + * @param options the request options + * @return a future notified when the WebSocket when connected + */ + Future connect(WebSocketConnectOptions options); + + /** + * Close the client immediately ({@code close(0, TimeUnit.SECONDS}). + * + * @return a future notified when the client is closed + */ + default Future close() { + return close(0, TimeUnit.SECONDS); + } + + /** + * Update the client with new SSL {@code options}, the update happens if the options object is valid and different + * from the existing options object. + * + * @param options the new SSL options + * @return a future signaling the update success + */ + default Future updateSSLOptions(ClientSSLOptions options) { + return updateSSLOptions(options, false); + } + + /** + *

Update the client with new SSL {@code options}, the update happens if the options object is valid and different + * from the existing options object. + * + *

The {@code options} object is compared using its {@code equals} method against the existing options to prevent + * an update when the objects are equals since loading options can be costly, this can happen for share TCP servers. + * When object are equals, setting {@code force} to {@code true} forces the update. + * + * @param options the new SSL options + * @param force force the update when options are equals + * @return a future signaling the update success + */ + Future updateSSLOptions(ClientSSLOptions options, boolean force); + + /** + * Initiate the client close sequence. + * + * @return a future notified when the client is closed + */ + Future close(long timeout, TimeUnit timeUnit); + +} diff --git a/src/main/java/io/vertx/core/http/WebSocketClientOptions.java b/src/main/java/io/vertx/core/http/WebSocketClientOptions.java new file mode 100644 index 00000000000..887ef118448 --- /dev/null +++ b/src/main/java/io/vertx/core/http/WebSocketClientOptions.java @@ -0,0 +1,672 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.http; + +import io.netty.handler.logging.ByteBufFormat; +import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.json.JsonObject; +import io.vertx.core.net.*; + +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import static io.vertx.core.http.HttpClientOptions.DEFAULT_NAME; +import static io.vertx.core.http.HttpClientOptions.DEFAULT_SHARED; + +@DataObject +@JsonGen +public class WebSocketClientOptions extends ClientOptionsBase { + + /** + * The default value for maximum WebSocket messages (could be assembled from multiple frames) is 4 full frames + * worth of data + */ + public static final int DEFAULT_MAX_MESSAGE_SIZE = 65536 * 4; + + /** + * The default value for the maximum number of WebSocket = 50 + */ + public static final int DEFAULT_MAX_CONNECTIONS = 50; + + /** + * Default WebSocket masked bit is true as depicted by RFC = {@code false} + */ + public static final boolean DEFAULT_SEND_UNMASKED_FRAMES = false; + + /** + * Default offer WebSocket per-frame deflate compression extension = {@code false} + */ + public static final boolean DEFAULT_TRY_USE_PER_FRAME_COMPRESSION = false; + + /** + * Default offer WebSocket per-message deflate compression extension = {@code false} + */ + public static final boolean DEFAULT_TRY_USE_PER_MESSAGE_COMPRESSION = false; + + /** + * Default WebSocket deflate compression level = 6 + */ + public static final int DEFAULT_COMPRESSION_LEVEL = 6; + + /** + * Default offering of the {@code server_no_context_takeover} WebSocket parameter deflate compression extension = {@code false} + */ + public static final boolean DEFAULT_ALLOW_CLIENT_NO_CONTEXT = false; + + /** + * Default offering of the {@code client_no_context_takeover} WebSocket parameter deflate compression extension = {@code false} + */ + public static final boolean DEFAULT_REQUEST_SERVER_NO_CONTEXT = false; + + /** + * Default WebSocket closing timeout = 10 second + */ + public static final int DEFAULT_CLOSING_TIMEOUT = 10; + + /** + * The default value for maximum WebSocket frame size = 65536 bytes + */ + public static final int DEFAULT_MAX_FRAME_SIZE = 65536; + + /** + * The default value for host name = "localhost" + */ + public static final String DEFAULT_DEFAULT_HOST = "localhost"; + + /** + * The default value for port = 80 + */ + public static final int DEFAULT_DEFAULT_PORT = 80; + + private String defaultHost; + private int defaultPort; + private boolean verifyHost; + private int maxFrameSize; + private int maxMessageSize; + private int maxConnections; + private boolean sendUnmaskedFrames; + private boolean tryUsePerFrameCompression; + private boolean tryUsePerMessageCompression; + private int compressionLevel; + private boolean allowClientNoContext; + private boolean requestServerNoContext; + private int closingTimeout; + private boolean shared; + private String name; + + /** + * Default constructor + */ + public WebSocketClientOptions() { + init(); + } + + /** + * Copy constructor + * + * @param other the options to copy + */ + public WebSocketClientOptions(WebSocketClientOptions other) { + super(other); + + this.defaultHost = other.defaultHost; + this.defaultPort = other.defaultPort; + this.verifyHost = other.verifyHost; + this.maxFrameSize = other.maxFrameSize; + this.maxMessageSize = other.maxMessageSize; + this.maxConnections = other.maxConnections; + this.sendUnmaskedFrames = other.sendUnmaskedFrames; + this.tryUsePerFrameCompression = other.tryUsePerFrameCompression; + this.tryUsePerMessageCompression = other.tryUsePerMessageCompression; + this.allowClientNoContext = other.allowClientNoContext; + this.compressionLevel = other.compressionLevel; + this.requestServerNoContext = other.requestServerNoContext; + this.closingTimeout = other.closingTimeout; + this.shared = other.shared; + this.name = other.name; + } + + /** + * Constructor to create an options from JSON + * + * @param json the JSON + */ + public WebSocketClientOptions(JsonObject json) { + super(json); + init(); + WebSocketClientOptionsConverter.fromJson(json, this); + } + + private void init() { + verifyHost = true; + defaultHost = DEFAULT_DEFAULT_HOST; + defaultPort = DEFAULT_DEFAULT_PORT; + maxFrameSize = DEFAULT_MAX_FRAME_SIZE; + maxMessageSize = DEFAULT_MAX_MESSAGE_SIZE; + maxConnections = DEFAULT_MAX_CONNECTIONS; + sendUnmaskedFrames = DEFAULT_SEND_UNMASKED_FRAMES; + tryUsePerFrameCompression = DEFAULT_TRY_USE_PER_FRAME_COMPRESSION; + tryUsePerMessageCompression = DEFAULT_TRY_USE_PER_MESSAGE_COMPRESSION; + compressionLevel = DEFAULT_COMPRESSION_LEVEL; + allowClientNoContext = DEFAULT_ALLOW_CLIENT_NO_CONTEXT; + requestServerNoContext = DEFAULT_REQUEST_SERVER_NO_CONTEXT; + closingTimeout = DEFAULT_CLOSING_TIMEOUT; + shared = DEFAULT_SHARED; + name = DEFAULT_NAME; + } + + /** + * Get the default host name to be used by this client in requests if none is provided when making the request. + * + * @return the default host name + */ + public String getDefaultHost() { + return defaultHost; + } + + /** + * Set the default host name to be used by this client in requests if none is provided when making the request. + * + * @return a reference to this, so the API can be used fluently + */ + public WebSocketClientOptions setDefaultHost(String defaultHost) { + this.defaultHost = defaultHost; + return this; + } + + /** + * Get the default port to be used by this client in requests if none is provided when making the request. + * + * @return the default port + */ + public int getDefaultPort() { + return defaultPort; + } + + /** + * Set the default port to be used by this client in requests if none is provided when making the request. + * + * @return a reference to this, so the API can be used fluently + */ + public WebSocketClientOptions setDefaultPort(int defaultPort) { + this.defaultPort = defaultPort; + return this; + } + + /** + * Is hostname verification (for SSL/TLS) enabled? + * + * @return {@code true} if enabled + */ + public boolean isVerifyHost() { + return verifyHost; + } + + /** + * Set whether hostname verification is enabled + * + * @param verifyHost {@code true} if enabled + * @return a reference to this, so the API can be used fluently + */ + public WebSocketClientOptions setVerifyHost(boolean verifyHost) { + this.verifyHost = verifyHost; + return this; + } + + /** + * @return {@code true} when frame masking is skipped + */ + public boolean isSendUnmaskedFrames() { + return sendUnmaskedFrames; + } + + /** + * Set {@code true} when the client wants to skip frame masking. + *

+ * You may want to set it {@code true} on server by server WebSocket communication: in this case you are by passing + * RFC6455 protocol. + *

+ * It's {@code false} as default. + * + * @param sendUnmaskedFrames true if enabled + * @return a reference to this, so the API can be used fluently + */ + public WebSocketClientOptions setSendUnmaskedFrames(boolean sendUnmaskedFrames) { + this.sendUnmaskedFrames = sendUnmaskedFrames; + return this; + } + + /** + * Get the maximum WebSocket frame size to use + * + * @return the max WebSocket frame size + */ + public int getMaxFrameSize() { + return maxFrameSize; + } + + /** + * Set the max WebSocket frame size + * + * @param maxFrameSize the max frame size, in bytes + * @return a reference to this, so the API can be used fluently + */ + public WebSocketClientOptions setMaxFrameSize(int maxFrameSize) { + this.maxFrameSize = maxFrameSize; + return this; + } + + /** + * Get the maximum WebSocket message size to use + * + * @return the max WebSocket message size + */ + public int getMaxMessageSize() { + return maxMessageSize; + } + + /** + * Set the max WebSocket message size + * + * @param maxMessageSize the max message size, in bytes + * @return a reference to this, so the API can be used fluently + */ + public WebSocketClientOptions setMaxMessageSize(int maxMessageSize) { + this.maxMessageSize = maxMessageSize; + return this; + } + + /** + * Get the maximum of WebSockets per endpoint. + * + * @return the max number of WebSockets + */ + public int getMaxConnections() { + return maxConnections; + } + + /** + * Set the max number of WebSockets per endpoint. + * + * @param maxConnections the max value + * @return a reference to this, so the API can be used fluently + */ + public WebSocketClientOptions setMaxConnections(int maxConnections) { + if (maxConnections == 0 || maxConnections < -1) { + throw new IllegalArgumentException("maxWebSockets must be > 0 or -1 (disabled)"); + } + this.maxConnections = maxConnections; + return this; + } + + /** + * Set whether the client will offer the WebSocket per-frame deflate compression extension. + * + * @param offer {@code true} to offer the per-frame deflate compression extension + * @return a reference to this, so the API can be used fluently + */ + public WebSocketClientOptions setTryUsePerFrameCompression(boolean offer) { + this.tryUsePerFrameCompression = offer; + return this; + } + + /** + * @return {@code true} when the WebSocket per-frame deflate compression extension will be offered + */ + public boolean getTryUsePerFrameCompression() { + return this.tryUsePerFrameCompression; + } + + /** + * Set whether the client will offer the WebSocket per-message deflate compression extension. + * + * @param offer {@code true} to offer the per-message deflate compression extension + * @return a reference to this, so the API can be used fluently + */ + public WebSocketClientOptions setTryUsePerMessageCompression(boolean offer) { + this.tryUsePerMessageCompression = offer; + return this; + } + + /** + * @return {@code true} when the WebSocket per-message deflate compression extension will be offered + */ + public boolean getTryUsePerMessageCompression() { + return this.tryUsePerMessageCompression; + } + + /** + * Set the WebSocket deflate compression level. + * + * @param compressionLevel the WebSocket deflate compression level + * @return a reference to this, so the API can be used fluently + */ + public WebSocketClientOptions setCompressionLevel(int compressionLevel) { + this.compressionLevel = compressionLevel; + return this; + } + + /** + * @return the WebSocket deflate compression level + */ + public int getCompressionLevel() { + return this.compressionLevel; + } + + /** + * Set whether the {@code client_no_context_takeover} parameter of the WebSocket per-message + * deflate compression extension will be offered. + * + * @param offer {@code true} to offer the {@code client_no_context_takeover} parameter + * @return a reference to this, so the API can be used fluently + */ + public WebSocketClientOptions setCompressionAllowClientNoContext(boolean offer) { + this.allowClientNoContext = offer; + return this; + } + + /** + * @return {@code true} when the {@code client_no_context_takeover} parameter for the WebSocket per-message + * deflate compression extension will be offered + */ + public boolean getCompressionAllowClientNoContext() { + return this.allowClientNoContext; + } + + /** + * Set whether the {@code server_no_context_takeover} parameter of the WebSocket per-message + * deflate compression extension will be offered. + * + * @param offer {@code true} to offer the {@code server_no_context_takeover} parameter + * @return a reference to this, so the API can be used fluently + */ + public WebSocketClientOptions setCompressionRequestServerNoContext(boolean offer) { + this.requestServerNoContext = offer; + return this; + } + + /** + * @return {@code true} when the {@code server_no_context_takeover} parameter for the WebSocket per-message + * deflate compression extension will be offered + */ + public boolean getCompressionRequestServerNoContext() { + return this.requestServerNoContext; + } + + /** + * @return the amount of time (in seconds) a client WebSocket will wait until it closes TCP connection after receiving a close frame + */ + public int getClosingTimeout() { + return closingTimeout; + } + + /** + * Set the amount of time a client WebSocket will wait until it closes the TCP connection after receiving a close frame. + * + *

When a WebSocket is closed, the server should close the TCP connection. This timeout will close + * the TCP connection on the client when it expires. + * + *

Set to {@code 0L} closes the TCP connection immediately after receiving the close frame. + * + *

Set to a negative value to disable it. + * + * @param closingTimeout the timeout is seconds + * @return a reference to this, so the API can be used fluently + */ + public WebSocketClientOptions setClosingTimeout(int closingTimeout) { + this.closingTimeout = closingTimeout; + return this; + } + + /** + * @return whether the pool is shared + */ + public boolean isShared() { + return shared; + } + + /** + * Set to {@code true} to share the client. + * + *

There can be multiple shared clients distinguished by {@link #getName()}, when no specific + * name is set, the {@link HttpClientOptions#DEFAULT_NAME} is used. + * + * @param shared {@code true} to use a shared client + * @return a reference to this, so the API can be used fluently + */ + public WebSocketClientOptions setShared(boolean shared) { + this.shared = shared; + return this; + } + + /** + * @return the client name used for sharing + */ + public String getName() { + return name; + } + + /** + * Set the client name, used when the client is shared, otherwise ignored. + * @param name the new name + * @return a reference to this, so the API can be used fluently + */ + public WebSocketClientOptions setName(String name) { + Objects.requireNonNull(name, "Client name cannot be null"); + this.name = name; + return this; + } + + /** + * Convert to JSON + * + * @return the JSON + */ + public JsonObject toJson() { + JsonObject json = super.toJson(); + WebSocketClientOptionsConverter.toJson(this, json); + return json; + } + + @Override + public WebSocketClientOptions setTrustAll(boolean trustAll) { + return (WebSocketClientOptions)super.setTrustAll(trustAll); + } + + @Override + public WebSocketClientOptions setConnectTimeout(int connectTimeout) { + return (WebSocketClientOptions)super.setConnectTimeout(connectTimeout); + } + + @Override + public WebSocketClientOptions setMetricsName(String metricsName) { + return (WebSocketClientOptions)super.setMetricsName(metricsName); + } + + @Override + public WebSocketClientOptions setProxyOptions(ProxyOptions proxyOptions) { + return (WebSocketClientOptions)super.setProxyOptions(proxyOptions); + } + + @Override + public WebSocketClientOptions setNonProxyHosts(List nonProxyHosts) { + return (WebSocketClientOptions)super.setNonProxyHosts(nonProxyHosts); + } + + @Override + public WebSocketClientOptions setLocalAddress(String localAddress) { + return (WebSocketClientOptions)super.setLocalAddress(localAddress); + } + + @Override + public WebSocketClientOptions setLogActivity(boolean logEnabled) { + return (WebSocketClientOptions)super.setLogActivity(logEnabled); + } + + @Override + public WebSocketClientOptions setActivityLogDataFormat(ByteBufFormat activityLogDataFormat) { + return (WebSocketClientOptions)super.setActivityLogDataFormat(activityLogDataFormat); + } + + @Override + public WebSocketClientOptions setTcpNoDelay(boolean tcpNoDelay) { + return (WebSocketClientOptions)super.setTcpNoDelay(tcpNoDelay); + } + + @Override + public WebSocketClientOptions setTcpKeepAlive(boolean tcpKeepAlive) { + return (WebSocketClientOptions)super.setTcpKeepAlive(tcpKeepAlive); + } + + @Override + public WebSocketClientOptions setSoLinger(int soLinger) { + return (WebSocketClientOptions)super.setSoLinger(soLinger); + } + + @Override + public WebSocketClientOptions setIdleTimeout(int idleTimeout) { + return (WebSocketClientOptions)super.setIdleTimeout(idleTimeout); + } + + @Override + public WebSocketClientOptions setReadIdleTimeout(int idleTimeout) { + return (WebSocketClientOptions)super.setReadIdleTimeout(idleTimeout); + } + + @Override + public WebSocketClientOptions setWriteIdleTimeout(int idleTimeout) { + return (WebSocketClientOptions)super.setWriteIdleTimeout(idleTimeout); + } + + @Override + public WebSocketClientOptions setIdleTimeoutUnit(TimeUnit idleTimeoutUnit) { + return (WebSocketClientOptions)super.setIdleTimeoutUnit(idleTimeoutUnit); + } + + @Override + public WebSocketClientOptions setSsl(boolean ssl) { + return (WebSocketClientOptions)super.setSsl(ssl); + } + + @Override + public WebSocketClientOptions setKeyCertOptions(KeyCertOptions options) { + return (WebSocketClientOptions)super.setKeyCertOptions(options); + } + + @Override + public WebSocketClientOptions setTrustOptions(TrustOptions options) { + return (WebSocketClientOptions)super.setTrustOptions(options); + } + + @Override + public WebSocketClientOptions setUseAlpn(boolean useAlpn) { + return (WebSocketClientOptions)super.setUseAlpn(useAlpn); + } + + @Override + public WebSocketClientOptions setSslEngineOptions(SSLEngineOptions sslEngineOptions) { + return (WebSocketClientOptions)super.setSslEngineOptions(sslEngineOptions); + } + + @Override + public WebSocketClientOptions setSendBufferSize(int sendBufferSize) { + return (WebSocketClientOptions)super.setSendBufferSize(sendBufferSize); + } + + @Override + public WebSocketClientOptions setReceiveBufferSize(int receiveBufferSize) { + return (WebSocketClientOptions)super.setReceiveBufferSize(receiveBufferSize); + } + + @Override + public WebSocketClientOptions setReuseAddress(boolean reuseAddress) { + return (WebSocketClientOptions)super.setReuseAddress(reuseAddress); + } + + @Override + public WebSocketClientOptions setReusePort(boolean reusePort) { + return (WebSocketClientOptions)super.setReusePort(reusePort); + } + + @Override + public WebSocketClientOptions setTrafficClass(int trafficClass) { + return (WebSocketClientOptions)super.setTrafficClass(trafficClass); + } + + @Override + public WebSocketClientOptions setTcpFastOpen(boolean tcpFastOpen) { + return (WebSocketClientOptions)super.setTcpFastOpen(tcpFastOpen); + } + + @Override + public WebSocketClientOptions setTcpCork(boolean tcpCork) { + return (WebSocketClientOptions)super.setTcpCork(tcpCork); + } + + @Override + public WebSocketClientOptions setTcpQuickAck(boolean tcpQuickAck) { + return (WebSocketClientOptions)super.setTcpQuickAck(tcpQuickAck); + } + + @Override + public WebSocketClientOptions setTcpUserTimeout(int tcpUserTimeout) { + return (WebSocketClientOptions)super.setTcpUserTimeout(tcpUserTimeout); + } + + @Override + public WebSocketClientOptions setEnabledSecureTransportProtocols(Set enabledSecureTransportProtocols) { + return (WebSocketClientOptions)super.setEnabledSecureTransportProtocols(enabledSecureTransportProtocols); + } + + @Override + public WebSocketClientOptions setSslHandshakeTimeout(long sslHandshakeTimeout) { + return (WebSocketClientOptions)super.setSslHandshakeTimeout(sslHandshakeTimeout); + } + + @Override + public WebSocketClientOptions setSslHandshakeTimeoutUnit(TimeUnit sslHandshakeTimeoutUnit) { + return (WebSocketClientOptions)super.setSslHandshakeTimeoutUnit(sslHandshakeTimeoutUnit); + } + + @Override + public WebSocketClientOptions addNonProxyHost(String host) { + return (WebSocketClientOptions)super.addNonProxyHost(host); + } + + @Override + public WebSocketClientOptions addEnabledCipherSuite(String suite) { + return (WebSocketClientOptions)super.addEnabledCipherSuite(suite); + } + + @Override + public WebSocketClientOptions removeEnabledCipherSuite(String suite) { + return (WebSocketClientOptions)super.removeEnabledCipherSuite(suite); + } + + @Override + public WebSocketClientOptions addCrlPath(String crlPath) throws NullPointerException { + return (WebSocketClientOptions)super.addCrlPath(crlPath); + } + + @Override + public WebSocketClientOptions addCrlValue(Buffer crlValue) throws NullPointerException { + return (WebSocketClientOptions)super.addCrlValue(crlValue); + } + + @Override + public WebSocketClientOptions addEnabledSecureTransportProtocol(String protocol) { + return (WebSocketClientOptions)super.addEnabledSecureTransportProtocol(protocol); + } +} diff --git a/src/main/java/io/vertx/core/http/WebSocketConnectOptions.java b/src/main/java/io/vertx/core/http/WebSocketConnectOptions.java index 87cc251424d..6a77ba947db 100644 --- a/src/main/java/io/vertx/core/http/WebSocketConnectOptions.java +++ b/src/main/java/io/vertx/core/http/WebSocketConnectOptions.java @@ -13,19 +13,28 @@ import io.netty.handler.codec.http.websocketx.WebSocketHandshakeException; import io.vertx.codegen.annotations.DataObject; import io.vertx.codegen.annotations.GenIgnore; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.MultiMap; import io.vertx.core.json.JsonObject; +import io.vertx.core.net.Address; +import io.vertx.core.net.ClientSSLOptions; import io.vertx.core.net.ProxyOptions; +import io.vertx.core.net.SocketAddress; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; +import java.net.URL; + /** * Options describing how an {@link HttpClient} connect a {@link WebSocket}. * * @author Julien Viet */ -@DataObject(generateConverter = true) +@DataObject +@JsonGen(publicConverter = false) public class WebSocketConnectOptions extends RequestOptions { /** @@ -184,6 +193,11 @@ public WebSocketConnectOptions setSsl(Boolean ssl) { return (WebSocketConnectOptions) super.setSsl(ssl); } + @Override + public WebSocketConnectOptions setSslOptions(ClientSSLOptions sslOptions) { + return (WebSocketConnectOptions) super.setSslOptions(sslOptions); + } + @Override public WebSocketConnectOptions setURI(String uri) { return (WebSocketConnectOptions) super.setURI(uri); @@ -201,6 +215,16 @@ public WebSocketConnectOptions setTimeout(long timeout) { return (WebSocketConnectOptions) super.setTimeout(timeout); } + @Override + public WebSocketConnectOptions setConnectTimeout(long timeout) { + return (WebSocketConnectOptions) super.setConnectTimeout(timeout); + } + + @Override + public WebSocketConnectOptions setIdleTimeout(long timeout) { + return (WebSocketConnectOptions) super.setIdleTimeout(timeout); + } + @Override public WebSocketConnectOptions addHeader(String key, String value) { return (WebSocketConnectOptions) super.addHeader(key, value); @@ -237,6 +261,76 @@ public WebSocketConnectOptions setHeaders(MultiMap headers) { return (WebSocketConnectOptions) super.setHeaders(headers); } + @Override + public WebSocketConnectOptions setServer(Address server) { + return (WebSocketConnectOptions) super.setServer(server); + } + + @Override + public WebSocketConnectOptions setMethod(HttpMethod method) { + return (WebSocketConnectOptions) super.setMethod(method); + } + + @Override + public WebSocketConnectOptions setFollowRedirects(Boolean followRedirects) { + return (WebSocketConnectOptions) super.setFollowRedirects(followRedirects); + } + + @Override + public WebSocketConnectOptions setAbsoluteURI(String absoluteURI) { + URI uri; + try { + uri = new URI(absoluteURI); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } + setAbsoluteURI(uri); + return this; + } + + @Override + public WebSocketConnectOptions setAbsoluteURI(URL url) { + URI uri; + try { + uri = url.toURI(); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } + setAbsoluteURI(uri); + return this; + } + + private void setAbsoluteURI(URI uri) { + String scheme = uri.getScheme(); + if (!"ws".equals(scheme) && !"wss".equals(scheme)) { + throw new IllegalArgumentException("Scheme: " + scheme); + } + boolean ssl = scheme.length() == 3; + int port = uri.getPort(); + if (port == -1) { + port = ssl ? 443 : 80; + }; + StringBuilder relativeUri = new StringBuilder(); + if (uri.getRawPath() != null) { + relativeUri.append(uri.getRawPath()); + } + if (uri.getRawQuery() != null) { + relativeUri.append('?').append(uri.getRawQuery()); + } + if (uri.getRawFragment() != null) { + relativeUri.append('#').append(uri.getRawFragment()); + } + setHost(uri.getHost()); + setPort(port); + setSsl(ssl); + setURI(relativeUri.toString()); + } + + @Override + public WebSocketConnectOptions setTraceOperation(String op) { + return (WebSocketConnectOptions) super.setTraceOperation(op); + } + @Override public JsonObject toJson() { JsonObject json = super.toJson(); diff --git a/src/main/java/io/vertx/core/http/impl/CleanableHttpClient.java b/src/main/java/io/vertx/core/http/impl/CleanableHttpClient.java index 37405b761bf..476a275f5c8 100644 --- a/src/main/java/io/vertx/core/http/impl/CleanableHttpClient.java +++ b/src/main/java/io/vertx/core/http/impl/CleanableHttpClient.java @@ -10,25 +10,19 @@ */ package io.vertx.core.http.impl; -import io.vertx.codegen.annotations.Fluent; -import io.vertx.codegen.annotations.GenIgnore; import io.vertx.core.Future; -import io.vertx.core.Handler; -import io.vertx.core.MultiMap; import io.vertx.core.Promise; import io.vertx.core.http.*; import io.vertx.core.impl.VertxInternal; -import io.vertx.core.net.Address; -import io.vertx.core.net.SSLOptions; +import io.vertx.core.net.ClientSSLOptions; +import io.vertx.core.net.HostAndPort; +import io.vertx.core.net.SocketAddress; import io.vertx.core.net.impl.NetClientInternal; import io.vertx.core.spi.metrics.Metrics; -import io.vertx.core.spi.resolver.AddressResolver; import java.lang.ref.Cleaner; -import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.BiFunction; -import java.util.function.Function; /** * A lightweight proxy of Vert.x {@link HttpClient} that can be collected by the garbage collector and release @@ -68,66 +62,8 @@ public Future request(RequestOptions options) { } @Override - public Future request(HttpMethod method, int port, String host, String requestURI) { - return delegate.request(method, port, host, requestURI); - } - - @Override - public Future request(HttpMethod method, String host, String requestURI) { - return delegate.request(method, host, requestURI); - } - - @Override - public Future request(HttpMethod method, String requestURI) { - return delegate.request(method, requestURI); - } - - @Override - public Future webSocket(int port, String host, String requestURI) { - return delegate.webSocket(port, host, requestURI); - } - - @Override - public Future webSocket(String host, String requestURI) { - return delegate.webSocket(host, requestURI); - } - - @Override - public Future webSocket(String requestURI) { - return delegate.webSocket(requestURI); - } - - @Override - public Future webSocket(WebSocketConnectOptions options) { - return delegate.webSocket(options); - } - - @Override - public Future webSocketAbs(String url, MultiMap headers, WebsocketVersion version, List subProtocols) { - return delegate.webSocketAbs(url, headers, version, subProtocols); - } - - @Override - public Future updateSSLOptions(SSLOptions options) { - return delegate.updateSSLOptions(options); - } - - @Override - @Fluent - public HttpClient connectionHandler(Handler handler) { - return delegate.connectionHandler(handler); - } - - @Override - @Fluent - public HttpClient redirectHandler(Function> handler) { - return delegate.redirectHandler(handler); - } - - @Override - @GenIgnore - public Function> redirectHandler() { - return delegate.redirectHandler(); + public Future updateSSLOptions(ClientSSLOptions options, boolean force) { + return delegate.updateSSLOptions(options, force); } @Override @@ -174,20 +110,13 @@ public Future closeFuture() { return delegate.closeFuture(); } - @Override - public void addressResolver(AddressResolver addressResolver) { - delegate.addressResolver(addressResolver); - } - - @Override - public Future request(Address address, HttpMethod method, int port, String host, String requestURI) { - return delegate.request(address, method, port, host, requestURI); - } - @Override public void close(Promise completion) { delegate.close(completion); } - + @Override + public Future connect(SocketAddress server, HostAndPort peer) { + return delegate.connect(server, peer); + } } diff --git a/src/main/java/io/vertx/core/http/impl/CleanableWebSocketClient.java b/src/main/java/io/vertx/core/http/impl/CleanableWebSocketClient.java new file mode 100644 index 00000000000..fb770614b2e --- /dev/null +++ b/src/main/java/io/vertx/core/http/impl/CleanableWebSocketClient.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2011-2022 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.http.impl; + +import io.vertx.core.Closeable; +import io.vertx.core.Future; +import io.vertx.core.Promise; +import io.vertx.core.http.*; +import io.vertx.core.net.ClientSSLOptions; +import io.vertx.core.spi.metrics.Metrics; +import io.vertx.core.spi.metrics.MetricsProvider; + +import java.lang.ref.Cleaner; +import java.util.concurrent.TimeUnit; +import java.util.function.BiFunction; + +/** + * A lightweight proxy of Vert.x {@link HttpClient} that can be collected by the garbage collector and release + * the resources when it happens with a {@code 30} seconds grace period. + * + * @author Julien Viet + */ +public class CleanableWebSocketClient implements WebSocketClient, MetricsProvider, Closeable { + + static class Action implements Runnable { + private final BiFunction> dispose; + private long timeout = 30L; + private TimeUnit timeUnit = TimeUnit.SECONDS; + private Future closeFuture; + private Action(BiFunction> dispose) { + this.dispose = dispose; + } + @Override + public void run() { + closeFuture = dispose.apply(timeout, timeUnit); + } + } + + public final WebSocketClient delegate; + private final Cleaner.Cleanable cleanable; + private final Action action; + + public CleanableWebSocketClient(WebSocketClient delegate, Cleaner cleaner, BiFunction> dispose) { + this.action = new Action(dispose); + this.delegate = delegate; + this.cleanable = cleaner.register(this, action); + } + + @Override + public ClientWebSocket webSocket() { + return delegate.webSocket(); + } + + public Future connect(WebSocketConnectOptions options) { + return delegate.connect(options); + } + + @Override + public Future updateSSLOptions(ClientSSLOptions options, boolean force) { + return delegate.updateSSLOptions(options, force); + } + + @Override + public Future close(long timeout, TimeUnit timeUnit) { + if (timeout < 0L) { + throw new IllegalArgumentException(); + } + if (timeUnit == null) { + throw new IllegalArgumentException(); + } + action.timeout = timeout; + action.timeUnit = timeUnit; + cleanable.clean(); + return action.closeFuture; + } + + @Override + public void close(Promise completion) { + ((Closeable)delegate).close(completion); + } + + @Override + public Metrics getMetrics() { + return ((MetricsProvider)delegate).getMetrics(); + } +} diff --git a/src/main/java/io/vertx/core/http/impl/ClientHttpEndpointBase.java b/src/main/java/io/vertx/core/http/impl/ClientHttpEndpointBase.java index babf7421a52..e0cad845cd0 100644 --- a/src/main/java/io/vertx/core/http/impl/ClientHttpEndpointBase.java +++ b/src/main/java/io/vertx/core/http/impl/ClientHttpEndpointBase.java @@ -12,13 +12,13 @@ import io.vertx.core.Future; import io.vertx.core.impl.ContextInternal; -import io.vertx.core.net.impl.pool.Endpoint; +import io.vertx.core.net.impl.endpoint.Endpoint; import io.vertx.core.spi.metrics.ClientMetrics; /** * @author Julien Viet */ -abstract class ClientHttpEndpointBase extends Endpoint { +abstract class ClientHttpEndpointBase extends Endpoint { private final ClientMetrics metrics; // Shall be removed later combining the PoolMetrics with HttpClientMetrics @@ -28,7 +28,6 @@ abstract class ClientHttpEndpointBase extends Endpoint { this.metrics = metrics; } - @Override public Future requestConnection(ContextInternal ctx, long timeout) { Future fut = requestConnection2(ctx, timeout); if (metrics != null) { diff --git a/src/main/java/io/vertx/core/http/impl/ClientWebSocketImpl.java b/src/main/java/io/vertx/core/http/impl/ClientWebSocketImpl.java new file mode 100644 index 00000000000..e844385173b --- /dev/null +++ b/src/main/java/io/vertx/core/http/impl/ClientWebSocketImpl.java @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2011-2034 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.http.impl; + +import io.vertx.codegen.annotations.Nullable; +import io.vertx.core.*; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.*; +import io.vertx.core.impl.ContextInternal; +import io.vertx.core.impl.VertxInternal; +import io.vertx.core.net.SocketAddress; + +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.security.cert.X509Certificate; +import java.security.cert.Certificate; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Client WebSocket implementation + */ +public class ClientWebSocketImpl implements ClientWebSocket { + + private WebSocketClientImpl client; + private final AtomicReference> connect = new AtomicReference<>(); + private volatile WebSocket ws; + private Handler exceptionHandler; + private Handler dataHandler; + private Handler endHandler; + private Handler closeHandler; + private Handler drainHandler; + private Handler frameHandler; + private Handler textMessageHandler; + private Handler binaryMessageHandler; + private Handler pongHandler; + + ClientWebSocketImpl(WebSocketClientImpl client) { + this.client = client; + } + + @Override + public Future connect(WebSocketConnectOptions options) { + ContextInternal ctx = client.vertx().getOrCreateContext(); + Promise promise = ctx.promise(); + if (!connect.compareAndSet(null, promise)) { + return ctx.failedFuture("Already connecting"); + } + client.webSocket(ctx, options, promise); + return promise + .future() + .andThen(ar -> { + if (ar.succeeded()) { + WebSocket w = ar.result(); + ws = w; + w.handler(dataHandler); + w.binaryMessageHandler(binaryMessageHandler); + w.textMessageHandler(textMessageHandler); + w.endHandler(endHandler); + w.closeHandler(closeHandler); + w.exceptionHandler(exceptionHandler); + w.drainHandler(drainHandler); + w.frameHandler(frameHandler); + w.pongHandler(pongHandler); + w.resume(); + } + }); + } + +// @Override +// public Future connect(String host, String requestURI) { +// return connect(client.options.getDefaultPort(), host, requestURI); +// } +// +// @Override +// public Future connect(String requestURI) { +// return connect(client.options.getDefaultPort(), client.options.getDefaultHost(), requestURI); +// } +// +// @Override +// public Future connect(String url, MultiMap headers, WebsocketVersion version, List subProtocols) { +// return connect(HttpClientImpl.webSocketConnectOptionsAbs(url, headers, version, subProtocols)); +// } + + @Override + public ClientWebSocket exceptionHandler(Handler handler) { + exceptionHandler = handler; + WebSocket w = ws; + if (w != null) { + w.exceptionHandler(handler); + } + return this; + } + + @Override + public ClientWebSocket handler(Handler handler) { + dataHandler = handler; + WebSocket w = ws; + if (w != null) { + w.handler(handler); + } + return this; + } + + @Override + public WebSocket pause() { + delegate().pause(); + return this; + } + + @Override + public WebSocket resume() { + delegate().resume(); + return this; + } + + @Override + public WebSocket fetch(long amount) { + delegate().fetch(amount); + return this; + } + + @Override + public ClientWebSocket endHandler(Handler handler) { + endHandler = handler; + WebSocket w = ws; + if (w != null) { + w.endHandler(handler); + } + return this; + } + + @Override + public WebSocket setWriteQueueMaxSize(int maxSize) { + delegate().setWriteQueueMaxSize(maxSize); + return this; + } + + @Override + public ClientWebSocket drainHandler(Handler handler) { + drainHandler = handler; + WebSocket w = ws; + if (w != null) { + w.drainHandler(handler); + } + return this; + } + + @Override + public ClientWebSocket closeHandler(Handler handler) { + closeHandler = handler; + WebSocket w = ws; + if (w != null) { + w.closeHandler(handler); + } + return this; + } + + @Override + public ClientWebSocket frameHandler(Handler handler) { + frameHandler = handler; + WebSocket w = ws; + if (w != null) { + w.frameHandler(handler); + } + return this; + } + + @Override + public String binaryHandlerID() { + return delegate().binaryHandlerID(); + } + + @Override + public String textHandlerID() { + return delegate().textHandlerID(); + } + + @Override + public String subProtocol() { + return delegate().subProtocol(); + } + + @Override + public Short closeStatusCode() { + return delegate().closeStatusCode(); + } + + @Override + public String closeReason() { + return delegate().closeReason(); + } + + @Override + public MultiMap headers() { + return delegate().headers(); + } + + @Override + public Future writeFrame(WebSocketFrame frame) { + return delegate().writeFrame(frame); + } + + @Override + public Future writeFinalTextFrame(String text) { + return delegate().writeFinalTextFrame(text); + } + + @Override + public Future writeFinalBinaryFrame(Buffer data) { + return delegate().writeFinalBinaryFrame(data); + } + + @Override + public Future writeBinaryMessage(Buffer data) { + return delegate().writeBinaryMessage(data); + } + + @Override + public Future writeTextMessage(String text) { + return delegate().writeTextMessage(text); + } + + @Override + public Future writePing(Buffer data) { + return delegate().writePing(data); + } + + @Override + public Future writePong(Buffer data) { + return delegate().writePong(data); + } + + @Override + public ClientWebSocket textMessageHandler(@Nullable Handler handler) { + textMessageHandler = handler; + WebSocket w = ws; + if (w != null) { + w.textMessageHandler(handler); + } + return this; + } + + @Override + public ClientWebSocket binaryMessageHandler(@Nullable Handler handler) { + binaryMessageHandler = handler; + WebSocket w = ws; + if (w != null) { + w.binaryMessageHandler(handler); + } + return this; + } + + @Override + public ClientWebSocket pongHandler(@Nullable Handler handler) { + pongHandler = handler; + WebSocket w = ws; + if (w != null) { + w.pongHandler(handler); + } + return this; + } + + @Override + public Future end() { + return delegate().end(); + } + + @Override + public Future close() { + return delegate().close(); + } + + @Override + public Future close(short statusCode) { + return delegate().close(statusCode); + } + + @Override + public Future close(short statusCode, @Nullable String reason) { + return delegate().close(statusCode, reason); + } + + @Override + public SocketAddress remoteAddress() { + return delegate().remoteAddress(); + } + + @Override + public SocketAddress localAddress() { + return delegate().localAddress(); + } + + @Override + public boolean isSsl() { + return delegate().isSsl(); + } + + @Override + public boolean isClosed() { + return delegate().isClosed(); + } + + @Override + public SSLSession sslSession() { + return delegate().sslSession(); + } + + @Override + public X509Certificate[] peerCertificateChain() throws SSLPeerUnverifiedException { + return delegate().peerCertificateChain(); + } + + @Override + public List peerCertificates() throws SSLPeerUnverifiedException { + return delegate().peerCertificates(); + } + + @Override + public Future write(Buffer data) { + return delegate().write(data); + } + + @Override + public boolean writeQueueFull() { + return delegate().writeQueueFull(); + } + + private WebSocket delegate() { + WebSocket w = ws; + if (w == null) { + throw new IllegalStateException("Not connected"); + } + return w; + } +} diff --git a/src/main/java/io/vertx/core/http/impl/EndpointKey.java b/src/main/java/io/vertx/core/http/impl/EndpointKey.java index 4127639be7e..64837c9233f 100644 --- a/src/main/java/io/vertx/core/http/impl/EndpointKey.java +++ b/src/main/java/io/vertx/core/http/impl/EndpointKey.java @@ -10,44 +10,54 @@ */ package io.vertx.core.http.impl; +import io.vertx.core.net.ClientSSLOptions; +import io.vertx.core.net.HostAndPort; import io.vertx.core.net.ProxyOptions; import io.vertx.core.net.SocketAddress; import java.util.Objects; -class EndpointKey { +final class EndpointKey { final boolean ssl; - final SocketAddress serverAddr; - final SocketAddress peerAddr; + final SocketAddress server; + final HostAndPort authority; final ProxyOptions proxyOptions; + final ClientSSLOptions sslOptions; - EndpointKey(boolean ssl, ProxyOptions proxyOptions, SocketAddress serverAddr, SocketAddress peerAddr) { - if (serverAddr == null) { + EndpointKey(boolean ssl, ClientSSLOptions sslOptions, ProxyOptions proxyOptions, SocketAddress server, HostAndPort authority) { + if (server == null) { throw new NullPointerException("No null server address"); } - if (peerAddr == null) { - throw new NullPointerException("No null peer address"); - } this.ssl = ssl; + this.sslOptions = sslOptions; this.proxyOptions = proxyOptions; - this.peerAddr = peerAddr; - this.serverAddr = serverAddr; + this.authority = authority; + this.server = server; } @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - EndpointKey that = (EndpointKey) o; - return ssl == that.ssl && serverAddr.equals(that.serverAddr) && peerAddr.equals(that.peerAddr) && equals(proxyOptions, that.proxyOptions); + if (this == o) { + return true; + }; + if (o instanceof EndpointKey) { + EndpointKey that = (EndpointKey) o; + return ssl == that.ssl && server.equals(that.server) && Objects.equals(authority, that.authority) && Objects.equals(sslOptions, that.sslOptions) && equals(proxyOptions, that.proxyOptions); + } + return false; } @Override public int hashCode() { int result = ssl ? 1 : 0; - result = 31 * result + peerAddr.hashCode(); - result = 31 * result + serverAddr.hashCode(); + result = 31 * result + server.hashCode(); + if (authority != null) { + result = 31 * result + authority.hashCode(); + } + if (sslOptions != null) { + result = 31 * result + sslOptions.hashCode(); + } if (proxyOptions != null) { result = 31 * result + hashCode(proxyOptions); } @@ -77,6 +87,6 @@ private static int hashCode(ProxyOptions options) { @Override public String toString() { - return serverAddr.toString(); + return server.toString(); } } diff --git a/src/main/java/io/vertx/core/http/impl/Http1xClientConnection.java b/src/main/java/io/vertx/core/http/impl/Http1xClientConnection.java index 7f1de2c9b67..4db88e2a49f 100644 --- a/src/main/java/io/vertx/core/http/impl/Http1xClientConnection.java +++ b/src/main/java/io/vertx/core/http/impl/Http1xClientConnection.java @@ -13,38 +13,17 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import io.netty.channel.*; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoop; import io.netty.handler.codec.DecoderResult; import io.netty.handler.codec.compression.Brotli; import io.netty.handler.codec.compression.ZlibCodecFactory; -import io.netty.handler.codec.http.DefaultHttpContent; -import io.netty.handler.codec.http.DefaultHttpHeaders; -import io.netty.handler.codec.http.DefaultHttpRequest; -import io.netty.handler.codec.http.DefaultLastHttpContent; -import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.HttpContent; -import io.netty.handler.codec.http.HttpContentDecompressor; -import io.netty.handler.codec.http.HttpHeaderNames; -import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpObject; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http.HttpUtil; -import io.netty.handler.codec.http.LastHttpContent; -import io.netty.handler.codec.http.websocketx.WebSocket07FrameDecoder; -import io.netty.handler.codec.http.websocketx.WebSocket08FrameDecoder; -import io.netty.handler.codec.http.websocketx.WebSocket13FrameDecoder; -import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker; -import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker00; -import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker07; -import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker08; -import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker13; -import io.netty.handler.codec.http.websocketx.WebSocketDecoderConfig; +import io.netty.handler.codec.http.*; import io.netty.handler.codec.http.websocketx.WebSocketFrame; -import io.netty.handler.codec.http.websocketx.WebSocketFrameDecoder; -import io.netty.handler.codec.http.websocketx.WebSocketHandshakeException; -import io.netty.handler.codec.http.websocketx.WebSocketVersion; +import io.netty.handler.codec.http.websocketx.*; import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandler; import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandshaker; import io.netty.handler.codec.http.websocketx.extensions.compression.DeflateFrameClientExtensionHandshaker; @@ -54,29 +33,20 @@ import io.netty.util.ReferenceCountUtil; import io.netty.util.concurrent.FutureListener; import io.netty.util.concurrent.GenericFutureListener; -import io.vertx.core.Future; -import io.vertx.core.Handler; -import io.vertx.core.MultiMap; -import io.vertx.core.Promise; -import io.vertx.core.VertxException; +import io.vertx.core.*; import io.vertx.core.buffer.Buffer; -import io.vertx.core.http.HttpClientOptions; -import io.vertx.core.http.HttpFrame; +import io.vertx.core.buffer.impl.BufferInternal; import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpVersion; -import io.vertx.core.http.StreamPriority; -import io.vertx.core.http.WebSocket; -import io.vertx.core.http.WebsocketVersion; +import io.vertx.core.http.*; import io.vertx.core.http.impl.headers.HeadersAdaptor; import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.future.PromiseInternal; import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; -import io.vertx.core.net.impl.ShutdownEvent; +import io.vertx.core.net.HostAndPort; import io.vertx.core.net.SocketAddress; -import io.vertx.core.net.impl.NetSocketImpl; -import io.vertx.core.net.impl.NetSocketInternal; -import io.vertx.core.net.impl.VertxHandler; +import io.vertx.core.net.impl.*; import io.vertx.core.spi.metrics.ClientMetrics; import io.vertx.core.spi.metrics.HttpClientMetrics; import io.vertx.core.spi.tracing.SpanKind; @@ -86,12 +56,7 @@ import io.vertx.core.streams.impl.InboundBuffer; import java.net.URI; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Deque; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.BiConsumer; import static io.netty.handler.codec.http.websocketx.WebSocketVersion.*; @@ -115,10 +80,11 @@ public class Http1xClientConnection extends Http1xConnectionBase throw new IllegalStateException("Invalid object " + msg); }; - private final HttpClientImpl client; + private final HttpClientBase client; private final HttpClientOptions options; private final boolean ssl; private final SocketAddress server; + private final HostAndPort authority; public final ClientMetrics metrics; private final HttpVersion version; private final long lowWaterMark; @@ -139,34 +105,38 @@ public class Http1xClientConnection extends Http1xConnectionBase private long expirationTimestamp; private int seq = 1; private long readWindow; - private long writeWindow; - private boolean writeOverflow; private Deque pendingFrames; private long lastResponseReceivedTimestamp; Http1xClientConnection(HttpVersion version, - HttpClientImpl client, - ChannelHandlerContext channel, + HttpClientBase client, + ChannelHandlerContext chctx, boolean ssl, SocketAddress server, + HostAndPort authority, ContextInternal context, ClientMetrics metrics) { - super(context, channel); + super(context, chctx); this.client = client; this.options = client.options(); this.ssl = ssl; this.server = server; + this.authority = authority; this.metrics = metrics; this.version = version; this.readWindow = 0L; - this.writeWindow = 0L; - this.highWaterMark = channel.channel().config().getWriteBufferHighWaterMark(); - this.lowWaterMark = channel.channel().config().getWriteBufferLowWaterMark(); + this.highWaterMark = chctx.channel().config().getWriteBufferHighWaterMark(); + this.lowWaterMark = chctx.channel().config().getWriteBufferLowWaterMark(); this.keepAliveTimeout = options.getKeepAliveTimeout(); this.expirationTimestamp = expirationTimestampOf(keepAliveTimeout); } + @Override + public HostAndPort authority() { + return authority; + } + @Override public HttpClientConnection evictionHandler(Handler handler) { evictionHandler = handler; @@ -194,16 +164,23 @@ public long concurrency() { return options.isPipelining() ? options.getPipeliningLimit() : 1; } + @Override + public synchronized long activeStreams() { + return requests.isEmpty() && responses.isEmpty() ? 0 : 1; + } + /** - * @return a raw {@code NetSocket} - for internal use + * @return a raw {@code NetSocket} - for internal use - must be called from event-loop */ public NetSocketInternal toNetSocket() { - removeChannelHandlers(); - NetSocketImpl socket = new NetSocketImpl(context, chctx, null, metrics(), false); - socket.metric(metric()); evictionHandler.handle(null); - chctx.pipeline().replace("handler", "handler", VertxHandler.create(ctx -> socket)); - return socket; + chctx.pipeline().replace("handler", "handler", VertxHandler.create(ctx -> { + NetSocketImpl socket = new NetSocketImpl(context, ctx, null, null, metrics(), false); + socket.metric(metric()); + return socket; + })); + VertxHandler handler = (VertxHandler) chctx.pipeline().get(VertxHandler.class); + return handler.getConnection(); } private HttpRequest createRequest( @@ -229,7 +206,7 @@ private HttpRequest createRequest( if (chunked) { HttpUtil.setTransferEncodingChunked(request, true); } - if (options.isTryUseCompression() && request.headers().get(ACCEPT_ENCODING) == null) { + if (options.isDecompressionSupported() && request.headers().get(ACCEPT_ENCODING) == null) { // if compression should be used but nothing is specified by the user support deflate and gzip. CharSequence acceptEncoding = determineCompressionAcceptEncoding(); request.headers().set(ACCEPT_ENCODING, acceptEncoding); @@ -285,7 +262,7 @@ private void beginRequest(Stream stream, HttpRequestHead request, boolean chunke stream.trace = tracer.sendRequest(stream.context, SpanKind.RPC, options.getTracingPolicy(), request, operation, headers, HttpUtils.CLIENT_HTTP_REQUEST_TAG_EXTRACTOR); } } - writeToChannel(nettyRequest, promise); + write(nettyRequest, false, promise); if (end) { endRequest(stream); } @@ -297,12 +274,12 @@ private void writeBuffer(Stream s, ByteBuf buff, boolean end, FutureListener close()) ); } else { - writeToChannel(msg); + write(msg, false, voidPromise); } } else { if (end) { @@ -314,7 +291,7 @@ private void writeBuffer(Stream s, ByteBuf buff, boolean end, FutureListener handler) { + writeToChannel(new MessageWrite() { + @Override + public void write() { + stream.request = request; + beginRequest(stream, request, chunked, buf, end, connect, handler); + } + @Override + public void cancel(Throwable cause) { + handler.fail(cause); + } + }); + } + + private void writeBuffer(Stream stream, ByteBuf buff, boolean end, PromiseInternal listener) { + writeToChannel(new MessageWrite() { + @Override + public void write() { + writeBuffer(stream, buff, end, (FutureListener)listener); + } + + @Override + public void cancel(Throwable cause) { + listener.fail(cause); + } + }); + } + private abstract static class Stream { protected final Promise promise; @@ -391,11 +396,13 @@ private abstract static class Stream { private Object trace; private Object metric; + private HttpRequestHead request; private HttpResponseHead response; private boolean responseEnded; private long bytesRead; private long bytesWritten; + Stream(ContextInternal context, Promise promise, int id) { this.context = context; this.id = id; @@ -416,7 +423,7 @@ Object trace() { abstract void handleHead(HttpResponseHead response); abstract void handleChunk(Buffer buff); abstract void handleEnd(LastHttpContent trailer); - abstract void handleWritabilityChanged(boolean writable); + abstract void handleWriteQueueDrained(Void v); abstract void handleException(Throwable cause); abstract void handleClosed(); @@ -432,7 +439,6 @@ private static class StreamImpl extends Stream implements HttpClientStream { private final InboundBuffer queue; private boolean reset; private boolean closed; - private HttpRequestHead request; private Handler headHandler; private Handler chunkHandler; private Handler endHandler; @@ -498,7 +504,7 @@ public WriteStream setWriteQueueMaxSize(int maxSize) { @Override public boolean writeQueueFull() { - return false; + return conn.writeQueueFull(); } @Override @@ -559,72 +565,23 @@ public ContextInternal getContext() { @Override public Future writeHead(HttpRequestHead request, boolean chunked, ByteBuf buf, boolean end, StreamPriority priority, boolean connect) { PromiseInternal promise = context.promise(); - writeHead(request, chunked, buf, end, connect, promise); + conn.writeHead(this, request, chunked, buf, end, connect, promise); return promise.future(); } - private void writeHead(HttpRequestHead request, boolean chunked, ByteBuf buf, boolean end, boolean connect, PromiseInternal handler) { - EventLoop eventLoop = conn.context.nettyEventLoop(); - if (eventLoop.inEventLoop()) { - this.request = request; - conn.beginRequest(this, request, chunked, buf, end, connect, handler); - } else { - eventLoop.execute(() -> writeHead(request, chunked, buf, end, connect, handler)); - } - } - @Override public Future writeBuffer(ByteBuf buff, boolean end) { if (buff != null || end) { PromiseInternal listener = context.promise(); - writeBuffer(buff, end, listener); + conn.writeBuffer(this, buff, end, listener); return listener.future(); } else { throw new IllegalStateException("???"); } } - private void writeBuffer(ByteBuf buff, boolean end, FutureListener listener) { - FutureListener l; - if (buff != null) { - int size = buff.readableBytes(); - l = future -> { - Handler drain; - synchronized (conn) { - conn.writeWindow -= size; - if (conn.writeOverflow && conn.writeWindow < conn.lowWaterMark) { - drain = drainHandler; - conn.writeOverflow = false; - } else { - drain = null; - } - } - if (drain != null) { - context.emit(drain); - } - if (listener != null) { - listener.operationComplete(future); - } - }; - synchronized (conn) { - conn.writeWindow += size; - if (conn.writeWindow > conn.highWaterMark) { - conn.writeOverflow = true; - } - } - } else { - l = listener; - } - EventLoop eventLoop = conn.context.nettyEventLoop(); - if (eventLoop.inEventLoop()) { - conn.writeBuffer(this, buff, end, l); - } else { - eventLoop.execute(() -> writeBuffer(buff, end, l)); - } - } - @Override - public void writeFrame(int type, int flags, ByteBuf payload) { + public Future writeFrame(int type, int flags, ByteBuf payload) { throw new IllegalStateException("Cannot write an HTTP/2 frame over an HTTP/1.x connection"); } @@ -635,9 +592,7 @@ public void doSetWriteQueueMaxSize(int size) { @Override public boolean isNotWritable() { - synchronized (conn) { - return conn.writeWindow > conn.highWaterMark; - } + return conn.writeQueueFull(); } @Override @@ -684,7 +639,14 @@ public void updatePriority(StreamPriority streamPriority) { } @Override - void handleWritabilityChanged(boolean writable) { + void handleWriteQueueDrained(Void v) { + Handler handler; + synchronized (conn) { + handler = drainHandler; + } + if (handler != null) { + context.dispatch(handler); + } } void handleContinue() { @@ -863,40 +825,13 @@ private void handleResponseBegin(Stream stream, HttpResponseHead response) { } else { HttpRequestHead request; synchronized (this) { - request = ((StreamImpl)stream).request; + request = stream.request; stream.response = response; - if (metrics != null) { metrics.responseBegin(stream.metric, response); } - - // - if (response.statusCode != 100 && request.method != HttpMethod.CONNECT) { - // See https://tools.ietf.org/html/rfc7230#section-6.3 - String responseConnectionHeader = response.headers.get(HttpHeaderNames.CONNECTION); - String requestConnectionHeader = request.headers != null ? request.headers.get(HttpHeaderNames.CONNECTION) : null; - // We don't need to protect against concurrent changes on forceClose as it only goes from false -> true - if (HttpHeaderValues.CLOSE.contentEqualsIgnoreCase(responseConnectionHeader) || HttpHeaderValues.CLOSE.contentEqualsIgnoreCase(requestConnectionHeader)) { - // In all cases, if we have a close connection option then we SHOULD NOT treat the connection as persistent - this.close = true; - } else if (response.version == HttpVersion.HTTP_1_0 && !HttpHeaderValues.KEEP_ALIVE.contentEqualsIgnoreCase(responseConnectionHeader)) { - // In the HTTP/1.0 case both request/response need a keep-alive connection header the connection to be persistent - // currently Vertx forces the Connection header if keepalive is enabled for 1.0 - this.close = true; - } - String keepAliveHeader = response.headers.get(HttpHeaderNames.KEEP_ALIVE); - if (keepAliveHeader != null) { - int timeout = HttpUtils.parseKeepAliveHeaderTimeout(keepAliveHeader); - if (timeout != -1) { - this.keepAliveTimeout = timeout; - } - } - } } - - // stream.handleHead(response); - if (isConnect) { if ((request.method == HttpMethod.CONNECT && response.statusCode == 200) || ( @@ -936,7 +871,7 @@ private void removeChannelHandlers() { } private void handleResponseChunk(Stream stream, ByteBuf chunk) { - Buffer buff = Buffer.buffer(VertxHandler.safeBuffer(chunk)); + Buffer buff = BufferInternal.buffer(VertxHandler.safeBuffer(chunk)); int len = buff.length(); receiveBytes(len); stream.bytesRead += len; @@ -945,19 +880,44 @@ private void handleResponseChunk(Stream stream, ByteBuf chunk) { private void handleResponseEnd(Stream stream, LastHttpContent trailer) { boolean check; + HttpResponseHead response ; synchronized (this) { - if (stream.response == null) { + response = stream.response; + if (response == null) { // 100-continue return; } responses.pop(); - close |= !options.isKeepAlive(); + HttpRequestHead request = stream.request; + if ((request.method != HttpMethod.CONNECT && response.statusCode != 101)) { + // See https://tools.ietf.org/html/rfc7230#section-6.3 + String responseConnectionHeader = response.headers.get(HttpHeaderNames.CONNECTION); + String requestConnectionHeader = request.headers != null ? request.headers.get(HttpHeaderNames.CONNECTION) : null; + // We don't need to protect against concurrent changes on forceClose as it only goes from false -> true + boolean close = !options.isKeepAlive(); + if (HttpHeaderValues.CLOSE.contentEqualsIgnoreCase(responseConnectionHeader) || HttpHeaderValues.CLOSE.contentEqualsIgnoreCase(requestConnectionHeader)) { + // In all cases, if we have a close connection option then we SHOULD NOT treat the connection as persistent + close = true; + } else if (response.version == HttpVersion.HTTP_1_0 && !HttpHeaderValues.KEEP_ALIVE.contentEqualsIgnoreCase(responseConnectionHeader)) { + // In the HTTP/1.0 case both request/response need a keep-alive connection header the connection to be persistent + // currently Vertx forces the Connection header if keepalive is enabled for 1.0 + close = true; + } + this.close = close; + String keepAliveHeader = response.headers.get(HttpHeaderNames.KEEP_ALIVE); + if (keepAliveHeader != null) { + int timeout = HttpUtils.parseKeepAliveHeaderTimeout(keepAliveHeader); + if (timeout != -1) { + this.keepAliveTimeout = timeout; + } + } + } stream.responseEnded = true; check = requests.peek() != stream; } VertxTracer tracer = context.tracer(); if (tracer != null) { - tracer.receiveResponse(stream.context, stream.response, stream.trace, null, HttpUtils.CLIENT_RESPONSE_TAG_EXTRACTOR); + tracer.receiveResponse(stream.context, response, stream.trace, null, HttpUtils.CLIENT_RESPONSE_TAG_EXTRACTOR); } if (metrics != null) { metrics.responseEnd(stream.metric, stream.bytesRead); @@ -974,17 +934,21 @@ public HttpClientMetrics metrics() { return client.metrics(); } - synchronized Future toWebSocket( + /** + * @return a future of a paused WebSocket + */ + synchronized void toWebSocket( ContextInternal context, String requestURI, MultiMap headers, boolean allowOriginHeader, + WebSocketClientOptions options, WebsocketVersion vers, List subProtocols, long handshakeTimeout, boolean registerWriteHandlers, - int maxWebSocketFrameSize) { - Promise promise = context.promise(); + int maxWebSocketFrameSize, + Promise promise) { try { URI wsuri = new URI(requestURI); if (!wsuri.isAbsolute()) { @@ -1012,7 +976,7 @@ synchronized Future toWebSocket( } ChannelPipeline p = chctx.channel().pipeline(); - ArrayList extensionHandshakers = initializeWebSocketExtensionHandshakers(client.options()); + ArrayList extensionHandshakers = initializeWebSocketExtensionHandshakers(options); if (!extensionHandshakers.isEmpty()) { p.addBefore("handler", "webSocketsExtensionsHandler", new WebSocketClientExtensionHandler( extensionHandshakers.toArray(new WebSocketClientExtensionHandshaker[0]))); @@ -1044,9 +1008,9 @@ synchronized Future toWebSocket( context, Http1xClientConnection.this, version != V00, - options.getWebSocketClosingTimeout(), - options.getMaxWebSocketFrameSize(), - options.getMaxWebSocketMessageSize(), + options.getClosingTimeout(), + options.getMaxFrameSize(), + options.getMaxMessageSize(), registerWriteHandlers); ws.headers(new HeadersAdaptor(future.getNow())); ws.subProtocol(handshaker.actualSubprotocol()); @@ -1056,7 +1020,7 @@ synchronized Future toWebSocket( if (metrics != null) { ws.setMetric(metrics.connected(ws)); } - promise.complete(ws); + ws.pause(); Deque toResubmit = pendingFrames; if (toResubmit != null) { pendingFrames = null; @@ -1065,6 +1029,7 @@ synchronized Future toWebSocket( handleWsFrame(frame); } } + promise.complete(ws); } else { close(); promise.fail(future.cause()); @@ -1073,7 +1038,6 @@ synchronized Future toWebSocket( } catch (Exception e) { handleException(e); } - return promise.future(); } static WebSocketClientHandshaker newHandshaker( @@ -1161,40 +1125,30 @@ protected FullHttpRequest newHandshakeRequest() { throw new WebSocketHandshakeException("Protocol version " + version + " not supported."); } - ArrayList initializeWebSocketExtensionHandshakers(HttpClientOptions options) { + ArrayList initializeWebSocketExtensionHandshakers(WebSocketClientOptions options) { ArrayList extensionHandshakers = new ArrayList<>(); - if (options.getTryWebSocketDeflateFrameCompression()) { - extensionHandshakers.add(new DeflateFrameClientExtensionHandshaker(options.getWebSocketCompressionLevel(), + if (options.getTryUsePerFrameCompression()) { + extensionHandshakers.add(new DeflateFrameClientExtensionHandshaker(options.getCompressionLevel(), false)); } - if (options.getTryUsePerMessageWebSocketCompression()) { - extensionHandshakers.add(new PerMessageDeflateClientExtensionHandshaker(options.getWebSocketCompressionLevel(), + if (options.getTryUsePerMessageCompression()) { + extensionHandshakers.add(new PerMessageDeflateClientExtensionHandshaker(options.getCompressionLevel(), ZlibCodecFactory.isSupportingWindowSizeAndMemLevel(), PerMessageDeflateServerExtensionHandshaker.MAX_WINDOW_SIZE, - options.getWebSocketCompressionAllowClientNoContext(), options.getWebSocketCompressionRequestServerNoContext())); + options.getCompressionAllowClientNoContext(), options.getCompressionRequestServerNoContext())); } return extensionHandshakers; } @Override - public void handleInterestedOpsChanged() { - boolean writable = !isNotWritable(); - ContextInternal context; - Handler handler; - synchronized (this) { - Stream current = requests.peek(); - if (current != null) { - context = current.context; - handler = current::handleWritabilityChanged; - } else if (webSocket != null) { - context = webSocket.context; - handler = webSocket::handleWritabilityChanged; - } else { - return; - } + protected void handleWriteQueueDrained() { + Stream s = requests.peek(); + if (s != null) { + s.context.execute(s::handleWriteQueueDrained); + } else { + super.handleWriteQueueDrained(); } - context.execute(writable, handler); } protected void handleClosed() { @@ -1253,23 +1207,23 @@ protected void handleIdle(IdleStateEvent event) { } @Override - protected void handleException(Throwable e) { + public void handleException(Throwable e) { super.handleException(e); - WebSocketImpl ws; LinkedHashSet allStreams = new LinkedHashSet<>(); synchronized (this) { - ws = webSocket; allStreams.addAll(requests); allStreams.addAll(responses); } - if (ws != null) { - ws.handleException(e); - } for (Stream stream : allStreams) { stream.handleException(e); } } + @Override + public Future createRequest(ContextInternal context) { + return ((HttpClientImpl)client).createRequest(this, context); + } + @Override public Future createStream(ContextInternal context) { PromiseInternal promise = context.promise(); diff --git a/src/main/java/io/vertx/core/http/impl/Http1xConnectionBase.java b/src/main/java/io/vertx/core/http/impl/Http1xConnectionBase.java index 6525bec227e..7c536649002 100644 --- a/src/main/java/io/vertx/core/http/impl/Http1xConnectionBase.java +++ b/src/main/java/io/vertx/core/http/impl/Http1xConnectionBase.java @@ -99,6 +99,13 @@ public Future close() { } } + @Override + protected void handleWriteQueueDrained() { + if (webSocket != null) { + webSocket.context.execute(webSocket::handleWriteQueueDrained); + } + } + @Override public Http1xConnectionBase closeHandler(Handler handler) { return (Http1xConnectionBase) super.closeHandler(handler); @@ -109,6 +116,11 @@ public Http1xConnectionBase exceptionHandler(Handler handler) { return (Http1xConnectionBase) super.exceptionHandler(handler); } + @Override + public void handleException(Throwable t) { + super.handleException(t); + } + @Override public HttpConnection goAway(long errorCode, int lastStreamId, Buffer debugData) { throw new UnsupportedOperationException("HTTP/1.x connections don't support GOAWAY"); diff --git a/src/main/java/io/vertx/core/http/impl/Http1xServerConnection.java b/src/main/java/io/vertx/core/http/impl/Http1xServerConnection.java index 652ae15e528..202fe120f47 100644 --- a/src/main/java/io/vertx/core/http/impl/Http1xServerConnection.java +++ b/src/main/java/io/vertx/core/http/impl/Http1xServerConnection.java @@ -18,18 +18,7 @@ import io.netty.channel.ChannelPromise; import io.netty.channel.EventLoop; import io.netty.handler.codec.DecoderResult; -import io.netty.handler.codec.http.DefaultFullHttpResponse; -import io.netty.handler.codec.http.DefaultHttpRequest; -import io.netty.handler.codec.http.EmptyHttpHeaders; -import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.HttpContent; -import io.netty.handler.codec.http.DefaultHttpContent; -import io.netty.handler.codec.http.HttpHeaderNames; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpObject; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http.LastHttpContent; +import io.netty.handler.codec.http.*; import io.netty.handler.codec.http.websocketx.WebSocketDecoderConfig; import io.netty.handler.codec.http.websocketx.WebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketHandshakeException; @@ -37,21 +26,23 @@ import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory; import io.netty.handler.codec.http.websocketx.WebSocketVersion; import io.netty.util.ReferenceCountUtil; +import io.netty.util.concurrent.FutureListener; import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.Promise; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; +import io.vertx.core.buffer.impl.BufferInternal; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.ServerWebSocket; import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.future.PromiseInternal; -import io.vertx.core.impl.logging.Logger; -import io.vertx.core.impl.logging.LoggerFactory; import io.vertx.core.net.NetSocket; +import io.vertx.core.net.impl.MessageWrite; import io.vertx.core.net.impl.NetSocketImpl; +import io.vertx.core.net.impl.SSLHelper; import io.vertx.core.net.impl.SslChannelProvider; import io.vertx.core.net.impl.VertxHandler; import io.vertx.core.spi.metrics.HttpServerMetrics; @@ -88,8 +79,6 @@ */ public class Http1xServerConnection extends Http1xConnectionBase implements HttpServerConnection { - private static final Logger log = LoggerFactory.getLogger(Http1xServerConnection.class); - private final String serverOrigin; private final Supplier streamContextSupplier; private final SslChannelProvider sslChannelProvider; @@ -98,17 +87,19 @@ public class Http1xServerConnection extends Http1xConnectionBase requestHandler; private Handler invalidRequestHandler; final HttpServerMetrics metrics; final boolean handle100ContinueAutomatically; final HttpServerOptions options; + final SSLHelper sslHelper; public Http1xServerConnection(Supplier streamContextSupplier, SslChannelProvider sslChannelProvider, + SSLHelper sslHelper, HttpServerOptions options, ChannelHandlerContext chctx, ContextInternal context, @@ -118,11 +109,12 @@ public Http1xServerConnection(Supplier streamContextSupplier, this.serverOrigin = serverOrigin; this.streamContextSupplier = streamContextSupplier; this.options = options; + this.sslHelper = sslHelper; this.sslChannelProvider = sslChannelProvider; this.metrics = metrics; this.handle100ContinueAutomatically = options.isHandle100ContinueAutomatically(); this.tracingPolicy = options.getTracingPolicy(); - this.writable = true; + this.keepAlive = true; } TracingPolicy tracingPolicy() { @@ -148,6 +140,10 @@ public HttpServerMetrics metrics() { public void handleMessage(Object msg) { assert msg != null; + if (requestInProgress == null && !keepAlive && webSocket == null) { + // Discard message + return; + } // fast-path first if (msg == LastHttpContent.EMPTY_LAST_CONTENT) { onEnd(); @@ -162,7 +158,8 @@ public void handleMessage(Object msg) { return; } responseInProgress = requestInProgress; - req.handleBegin(writable); + keepAlive = HttpUtils.isKeepAlive(request); + req.handleBegin(keepAlive); Handler handler = request.decoderResult().isSuccess() ? requestHandler : invalidRequestHandler; req.context.emit(req, handler); } else { @@ -191,7 +188,7 @@ private void onContent(Object msg) { handleError(content); return; } - Buffer buffer = Buffer.buffer(VertxHandler.safeBuffer(content.content())); + Buffer buffer = BufferInternal.buffer(VertxHandler.safeBuffer(content.content())); Http1xServerRequest request; synchronized (this) { request = requestInProgress; @@ -204,12 +201,39 @@ private void onContent(Object msg) { } private void onEnd() { + boolean close; Http1xServerRequest request; synchronized (this) { request = requestInProgress; requestInProgress = null; + close = !keepAlive && responseInProgress == null; } request.context.execute(request, Http1xServerRequest::handleEnd); + if (close) { + flushAndClose(); + } + } + + private void flushAndClose() { + ChannelPromise channelFuture = channelFuture(); + writeToChannel(Unpooled.EMPTY_BUFFER, channelFuture); + channelFuture.addListener(fut -> close()); + } + + void write(HttpObject msg, PromiseInternal promise) { + writeToChannel(new MessageWrite() { + @Override + public void write() { + Http1xServerConnection.this.write(msg, false, promise == null ? voidPromise : wrap(promise)); + if (msg instanceof LastHttpContent) { + responseComplete(); + } + } + @Override + public void cancel(Throwable cause) { + promise.fail(cause); + } + }); } void responseComplete() { @@ -222,10 +246,18 @@ void responseComplete() { responseInProgress = null; DecoderResult result = request.decoderResult(); if (result.isSuccess()) { - Http1xServerRequest next = request.next(); - if (next != null) { - // Handle pipelined request - handleNext(next); + if (keepAlive) { + Http1xServerRequest next = request.next(); + if (next != null) { + // Handle pipelined request + handleNext(next); + } + } else { + if (requestInProgress == request || webSocket != null) { + // Deferred + } else { + flushAndClose(); + } } } else { ChannelPromise channelFuture = channelFuture(); @@ -239,7 +271,8 @@ void responseComplete() { private void handleNext(Http1xServerRequest next) { responseInProgress = next; - next.handleBegin(writable); + keepAlive = HttpUtils.isKeepAlive(next.nettyRequest()); + next.handleBegin(keepAlive); next.context.emit(next, next_ -> { next_.resume(); Handler handler = next_.nettyRequest().decoderResult().isSuccess() ? requestHandler : invalidRequestHandler; @@ -404,7 +437,7 @@ void netSocket(Promise promise) { } pipeline.replace("handler", "handler", VertxHandler.create(ctx -> { - NetSocketImpl socket = new NetSocketImpl(context, ctx, sslChannelProvider, metrics, false) { + NetSocketImpl socket = new NetSocketImpl(context, ctx, sslHelper, options.getSslOptions(), metrics, false) { @Override protected void handleClosed() { if (metrics != null) { @@ -437,26 +470,19 @@ public synchronized void handleMessage(Object msg) { } @Override - public void handleInterestedOpsChanged() { - writable = !isNotWritable(); - ContextInternal context; - Handler handler; - synchronized (this) { - if (responseInProgress != null) { - context = responseInProgress.context; - handler = responseInProgress.response()::handleWritabilityChanged; - } else if (webSocket != null) { - context = webSocket.context; - handler = webSocket::handleWritabilityChanged; - } else { - return; - } + protected void handleWriteQueueDrained() { + if (responseInProgress != null) { + ContextInternal context = responseInProgress.context; + Handler handler = responseInProgress.response()::handleWriteQueueDrained; + context.execute(handler); + } else { + super.handleWriteQueueDrained(); } - context.execute(writable, handler); } - void write100Continue() { - chctx.writeAndFlush(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE)); + void write100Continue(FutureListener listener) { + ChannelPromise promise = listener == null ? chctx.voidPromise() : chctx.newPromise().addListener(listener); + chctx.writeAndFlush(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE), promise); } void write103EarlyHints(HttpHeaders headers, PromiseInternal promise) { @@ -494,13 +520,11 @@ protected void handleClosed() { } @Override - protected void handleException(Throwable t) { + public void handleException(Throwable t) { super.handleException(t); Http1xServerRequest responseInProgress; Http1xServerRequest requestInProgress; - ServerWebSocketImpl ws; synchronized (this) { - ws = this.webSocket; requestInProgress = this.requestInProgress; responseInProgress = this.responseInProgress; if (METRICS_ENABLED && metrics != null) { @@ -513,9 +537,6 @@ protected void handleException(Throwable t) { if (responseInProgress != null && responseInProgress != requestInProgress) { responseInProgress.handleException(t); } - if (ws != null) { - ws.context.execute(v -> ws.handleException(t)); - } } @Override diff --git a/src/main/java/io/vertx/core/http/impl/Http1xServerRequest.java b/src/main/java/io/vertx/core/http/impl/Http1xServerRequest.java index 85698bd916b..f1ec6a507fb 100644 --- a/src/main/java/io/vertx/core/http/impl/Http1xServerRequest.java +++ b/src/main/java/io/vertx/core/http/impl/Http1xServerRequest.java @@ -17,12 +17,12 @@ import io.netty.handler.codec.http.multipart.Attribute; import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; import io.netty.handler.codec.http.multipart.InterfaceHttpData; -import io.vertx.codegen.annotations.Nullable; import io.vertx.core.Context; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.MultiMap; import io.vertx.core.buffer.Buffer; +import io.vertx.core.buffer.impl.BufferInternal; import io.vertx.core.http.*; import io.vertx.core.http.Cookie; import io.vertx.core.http.HttpVersion; @@ -32,6 +32,7 @@ import io.vertx.core.impl.future.PromiseInternal; import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; +import io.vertx.core.net.HostAndPort; import io.vertx.core.net.NetSocket; import io.vertx.core.net.SocketAddress; import io.vertx.core.spi.metrics.HttpServerMetrics; @@ -71,6 +72,7 @@ public class Http1xServerRequest extends HttpServerRequestInternal implements io private HttpRequest request; private io.vertx.core.http.HttpVersion version; private io.vertx.core.http.HttpMethod method; + private HostAndPort authority; private String uri; private String path; private String query; @@ -152,11 +154,11 @@ void handleContent(Buffer buffer) { } } - void handleBegin(boolean writable) { + void handleBegin(boolean keepAlive) { if (METRICS_ENABLED) { reportRequestBegin(); } - response = new Http1xServerResponse((VertxInternal) conn.vertx(), context, conn, request, metric, writable); + response = new Http1xServerResponse((VertxInternal) conn.vertx(), context, conn, request, metric, keepAlive); if (conn.handle100ContinueAutomatically) { check100(); } @@ -184,7 +186,7 @@ Http1xServerRequest next() { private void check100() { if (HttpUtil.is100ContinueExpected(request)) { - conn.write100Continue(); + conn.write100Continue(null); } } @@ -252,8 +254,14 @@ public String query() { } @Override - public @Nullable String host() { - return getHeader(HttpHeaderNames.HOST); + public synchronized HostAndPort authority() { + if (authority == null) { + String host = getHeader(HttpHeaderNames.HOST); + if (host != null) { + authority = HostAndPort.parseAuthority(host, -1); + } + } + return authority; } @Override @@ -384,7 +392,7 @@ public String absoluteURI() { @Override public SocketAddress remoteAddress() { - return conn.remoteAddress(); + return super.remoteAddress(); } @Override @@ -439,7 +447,7 @@ Future webSocket() { * Handle the request when a WebSocket upgrade header is present. */ private void webSocket(PromiseInternal promise) { - Buffer body = Buffer.buffer(); + BufferInternal body = BufferInternal.buffer(); boolean[] failed = new boolean[1]; handler(buff -> { if (!failed[0]) { @@ -448,7 +456,7 @@ private void webSocket(PromiseInternal promise) { failed[0] = true; // Request Entity Too Large response.setStatusCode(413).end(); - response.close(); + conn.close(); } } }); @@ -540,7 +548,7 @@ private void onData(Buffer data) { bytesRead += data.length(); if (decoder != null) { try { - decoder.offer(new DefaultHttpContent(data.getByteBuf())); + decoder.offer(new DefaultHttpContent(((BufferInternal)data).getByteBuf())); } catch (HttpPostRequestDecoder.ErrorDataDecoderException e) { handleException(e); } diff --git a/src/main/java/io/vertx/core/http/impl/Http1xServerRequestHandler.java b/src/main/java/io/vertx/core/http/impl/Http1xServerRequestHandler.java index f7a520f1fd2..f29ffb3867b 100644 --- a/src/main/java/io/vertx/core/http/impl/Http1xServerRequestHandler.java +++ b/src/main/java/io/vertx/core/http/impl/Http1xServerRequestHandler.java @@ -62,7 +62,7 @@ public void handle(HttpServerRequest req) { } else if (req.version() == null) { // Invalid HTTP version, i.e not HTTP/1.1 or HTTP/1.0 req.response().setStatusCode(501).end(); - req.response().close(); + req.connection().close(); } else { reqHandler.handle(req); } diff --git a/src/main/java/io/vertx/core/http/impl/Http1xServerResponse.java b/src/main/java/io/vertx/core/http/impl/Http1xServerResponse.java index d682c498435..3ef1f2f461a 100644 --- a/src/main/java/io/vertx/core/http/impl/Http1xServerResponse.java +++ b/src/main/java/io/vertx/core/http/impl/Http1xServerResponse.java @@ -14,7 +14,6 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelPromise; import io.netty.handler.codec.http.DefaultHttpContent; import io.netty.handler.codec.http.EmptyHttpHeaders; import io.netty.handler.codec.http.HttpObject; @@ -22,13 +21,14 @@ import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.LastHttpContent; +import io.netty.util.concurrent.FutureListener; import io.vertx.codegen.annotations.Nullable; -import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.MultiMap; import io.vertx.core.Promise; import io.vertx.core.buffer.Buffer; +import io.vertx.core.buffer.impl.BufferInternal; import io.vertx.core.http.Cookie; import io.vertx.core.http.HttpClosedException; import io.vertx.core.http.HttpHeaders; @@ -40,12 +40,12 @@ import io.vertx.core.impl.future.PromiseInternal; import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; +import io.vertx.core.net.HostAndPort; import io.vertx.core.net.NetSocket; import io.vertx.core.spi.metrics.Metrics; import io.vertx.core.spi.observability.HttpResponse; import java.io.File; -import java.io.FileNotFoundException; import java.io.RandomAccessFile; import java.util.Set; @@ -65,7 +65,7 @@ */ public class Http1xServerResponse implements HttpServerResponse, HttpResponse { - private static final Buffer EMPTY_BUFFER = Buffer.buffer(Unpooled.EMPTY_BUFFER); + private static final Buffer EMPTY_BUFFER = BufferInternal.buffer(Unpooled.EMPTY_BUFFER); private static final Logger log = LoggerFactory.getLogger(Http1xServerResponse.class); private static final String RESPONSE_WRITTEN = "Response has already been written"; @@ -87,7 +87,6 @@ public class Http1xServerResponse implements HttpServerResponse, HttpResponse { private Handler endHandler; private Handler headersEndHandler; private Handler bodyEndHandler; - private boolean writable; private boolean closed; private final HeadersMultiMap headers; private CookieJar cookies; @@ -102,7 +101,7 @@ public class Http1xServerResponse implements HttpServerResponse, HttpResponse { Http1xServerConnection conn, HttpRequest request, Object requestMetric, - boolean writable) { + boolean keepAlive) { this.vertx = vertx; this.conn = conn; this.context = context; @@ -111,9 +110,7 @@ public class Http1xServerResponse implements HttpServerResponse, HttpResponse { this.request = request; this.status = HttpResponseStatus.OK; this.requestMetric = requestMetric; - this.writable = writable; - this.keepAlive = (version == HttpVersion.HTTP_1_1 && !request.headers().contains(io.vertx.core.http.HttpHeaders.CONNECTION, HttpHeaders.CLOSE, true)) - || (version == HttpVersion.HTTP_1_0 && request.headers().contains(io.vertx.core.http.HttpHeaders.CONNECTION, HttpHeaders.KEEP_ALIVE, true)); + this.keepAlive = keepAlive; this.head = request.method() == io.netty.handler.codec.http.HttpMethod.HEAD; } @@ -270,7 +267,7 @@ public HttpServerResponse setWriteQueueMaxSize(int size) { public boolean writeQueueFull() { synchronized (conn) { checkValid(); - return !writable; + return conn.writeQueueFull(); } } @@ -321,28 +318,29 @@ public HttpServerResponse endHandler(@Nullable Handler handler) { @Override public Future write(Buffer chunk) { PromiseInternal promise = context.promise(); - write(chunk.getByteBuf(), promise); + write(((BufferInternal)chunk).getByteBuf(), promise); return promise.future(); } @Override public Future write(String chunk, String enc) { PromiseInternal promise = context.promise(); - write(Buffer.buffer(chunk, enc).getByteBuf(), promise); + write(BufferInternal.buffer(chunk, enc).getByteBuf(), promise); return promise.future(); } @Override public Future write(String chunk) { PromiseInternal promise = context.promise(); - write(Buffer.buffer(chunk).getByteBuf(), promise); + write(BufferInternal.buffer(chunk).getByteBuf(), promise); return promise.future(); } @Override - public HttpServerResponse writeContinue() { - conn.write100Continue(); - return this; + public Future writeContinue() { + Promise promise = context.promise(); + conn.write100Continue((FutureListener) promise); + return promise.future(); } @Override @@ -385,7 +383,7 @@ private void end(Buffer chunk, PromiseInternal listener) { throw new IllegalStateException(RESPONSE_WRITTEN); } written = true; - ByteBuf data = chunk.getByteBuf(); + ByteBuf data = ((BufferInternal)chunk).getByteBuf(); bytesWritten += data.readableBytes(); HttpObject msg; if (!headWritten) { @@ -396,8 +394,7 @@ private void end(Buffer chunk, PromiseInternal listener) { } else { msg = new AssembledLastHttpContent(data, trailingHeaders); } - conn.writeToChannel(msg, listener); - conn.responseComplete(); + conn.write(msg, listener); if (bodyEndHandler != null) { bodyEndHandler.handle(null); } @@ -405,8 +402,7 @@ private void end(Buffer chunk, PromiseInternal listener) { endHandler.handle(null); } if (!keepAlive) { - closeConnAfterWrite(); - closed = true; + closed = true; // ????? } } } @@ -423,20 +419,6 @@ void completeHandshake() { conn.responseComplete(); } - @Override - public void close() { - synchronized (conn) { - if (!closed) { - if (headWritten) { - closeConnAfterWrite(); - } else { - conn.close(); - } - closed = true; - } - } - } - @Override public Future end() { return end(EMPTY_BUFFER); @@ -469,20 +451,14 @@ public Future sendFile(String filename, long offset, long length) { bytesWritten = actualLength; written = true; - conn.writeToChannel(new AssembledHttpResponse(head, version, status, headers)); + conn.write(new AssembledHttpResponse(head, version, status, headers), null); ChannelFuture channelFut = conn.sendFile(raf, actualOffset, actualLength); channelFut.addListener(future -> { // write an empty last content to let the http encoder know the response is complete if (future.isSuccess()) { - ChannelPromise pr = conn.channelHandlerContext().newPromise(); - conn.writeToChannel(LastHttpContent.EMPTY_LAST_CONTENT, pr); - if (!keepAlive) { - pr.addListener(a -> { - closeConnAfterWrite(); - }); - } + conn.write(LastHttpContent.EMPTY_LAST_CONTENT, null); } // signal body end handler @@ -495,7 +471,7 @@ public Future sendFile(String filename, long offset, long length) { } // allow to write next response - conn.responseComplete(); + // conn.responseComplete(); // signal end handler Handler end; @@ -557,23 +533,14 @@ public HttpServerResponse bodyEndHandler(Handler handler) { } } - private void closeConnAfterWrite() { - ChannelPromise channelFuture = conn.channelFuture(); - conn.writeToChannel(Unpooled.EMPTY_BUFFER, channelFuture); - channelFuture.addListener(fut -> conn.close()); - } - - void handleWritabilityChanged(boolean writable) { + void handleWriteQueueDrained(Void v) { Handler handler; synchronized (conn) { - boolean skip = this.writable && !writable; - this.writable = writable; handler = drainHandler; - if (handler == null || skip) { - return; - } } - context.dispatch(null, handler); + if (handler != null) { + context.dispatch(null, handler); + } } void handleException(Throwable t) { @@ -690,7 +657,7 @@ private Http1xServerResponse write(ByteBuf chunk, PromiseInternal promise) } else { msg = new DefaultHttpContent(chunk); } - conn.writeToChannel(msg, promise); + conn.write(msg, promise); return this; } } @@ -707,7 +674,7 @@ Future netSocket(HttpMethod requestMethod, MultiMap requestHeaders) { status = requestMethod == HttpMethod.CONNECT ? HttpResponseStatus.OK : HttpResponseStatus.SWITCHING_PROTOCOLS; prepareHeaders(-1); PromiseInternal upgradePromise = context.promise(); - conn.writeToChannel(new AssembledHttpResponse(head, version, status, headers), upgradePromise); + conn.write(new AssembledHttpResponse(head, version, status, headers), upgradePromise); written = true; Promise promise = context.promise(); netSocket = promise.future(); @@ -729,18 +696,23 @@ public boolean reset(long code) { return false; } } - close(); + conn.close(); return true; } + @Override + public Future push(HttpMethod method, HostAndPort authority, String path, MultiMap headers) { + return context.failedFuture("HTTP/1 does not support response push"); + } + @Override public Future push(HttpMethod method, String host, String path, MultiMap headers) { return context.failedFuture("HTTP/1 does not support response push"); } @Override - public HttpServerResponse writeCustomFrame(int type, int flags, Buffer payload) { - return this; + public Future writeCustomFrame(int type, int flags, Buffer payload) { + return context.failedFuture("HTTP/1 does not support custom frames"); } CookieJar cookies() { diff --git a/src/main/java/io/vertx/core/http/impl/Http1xUpgradeToH2CHandler.java b/src/main/java/io/vertx/core/http/impl/Http1xUpgradeToH2CHandler.java index b6958f63927..e358390c822 100644 --- a/src/main/java/io/vertx/core/http/impl/Http1xUpgradeToH2CHandler.java +++ b/src/main/java/io/vertx/core/http/impl/Http1xUpgradeToH2CHandler.java @@ -21,6 +21,7 @@ import io.netty.handler.codec.http2.*; import io.netty.handler.timeout.IdleState; import io.netty.handler.timeout.IdleStateEvent; +import io.vertx.core.net.impl.SSLHelper; import io.vertx.core.net.impl.SslChannelProvider; import io.vertx.core.net.impl.VertxHandler; @@ -34,13 +35,15 @@ public class Http1xUpgradeToH2CHandler extends ChannelInboundHandlerAdapter { private final HttpServerWorker initializer; private final SslChannelProvider sslChannelProvider; + private final SSLHelper sslHelper; private VertxHttp2ConnectionHandler handler; private final boolean isCompressionSupported; private final boolean isDecompressionSupported; - Http1xUpgradeToH2CHandler(HttpServerWorker initializer, SslChannelProvider sslChannelProvider, boolean isCompressionSupported, boolean isDecompressionSupported) { + Http1xUpgradeToH2CHandler(HttpServerWorker initializer, SslChannelProvider sslChannelProvider, SSLHelper sslHelper, boolean isCompressionSupported, boolean isDecompressionSupported) { this.initializer = initializer; this.sslChannelProvider = sslChannelProvider; + this.sslHelper = sslHelper; this.isCompressionSupported = isCompressionSupported; this.isDecompressionSupported = isDecompressionSupported; } @@ -120,7 +123,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception ctx.writeAndFlush(res); } } else { - initializer.configureHttp1(ctx.pipeline(), sslChannelProvider); + initializer.configureHttp1Handler(ctx.pipeline(), sslChannelProvider, sslHelper); ctx.fireChannelRead(msg); ctx.pipeline().remove(this); } @@ -140,7 +143,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception pipeline.remove(handler.getKey()); } } - initializer.configureHttp2(pipeline); + initializer.configureHttp2Pipeline(pipeline); } } else { // We might have left over buffer sent when removing the HTTP decoder that needs to be propagated to the HTTP handler diff --git a/src/main/java/io/vertx/core/http/impl/Http2ClientConnection.java b/src/main/java/io/vertx/core/http/impl/Http2ClientConnection.java index 3b0beca28f7..c3dc2287b00 100644 --- a/src/main/java/io/vertx/core/http/impl/Http2ClientConnection.java +++ b/src/main/java/io/vertx/core/http/impl/Http2ClientConnection.java @@ -15,11 +15,7 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http2.DefaultHttp2Headers; -import io.netty.handler.codec.http2.Http2Error; -import io.netty.handler.codec.http2.Http2Exception; -import io.netty.handler.codec.http2.Http2Headers; -import io.netty.handler.codec.http2.Http2Stream; +import io.netty.handler.codec.http2.*; import io.netty.handler.timeout.IdleStateEvent; import io.vertx.core.*; import io.vertx.core.buffer.Buffer; @@ -27,8 +23,9 @@ import io.vertx.core.http.impl.headers.HeadersMultiMap; import io.vertx.core.http.impl.headers.Http2HeadersAdaptor; import io.vertx.core.impl.ContextInternal; -import io.vertx.core.impl.EventLoopContext; import io.vertx.core.impl.future.PromiseInternal; +import io.vertx.core.net.HostAndPort; +import io.vertx.core.net.impl.MessageWrite; import io.vertx.core.spi.metrics.ClientMetrics; import io.vertx.core.spi.metrics.HttpClientMetrics; import io.vertx.core.spi.tracing.SpanKind; @@ -43,20 +40,28 @@ */ class Http2ClientConnection extends Http2ConnectionBase implements HttpClientConnection { - private final HttpClientImpl client; + private final HttpClientBase client; private final ClientMetrics metrics; + private final HostAndPort authority; private Handler evictionHandler = DEFAULT_EVICTION_HANDLER; private Handler concurrencyChangeHandler = DEFAULT_CONCURRENCY_CHANGE_HANDLER; private long expirationTimestamp; private boolean evicted; - Http2ClientConnection(HttpClientImpl client, - EventLoopContext context, + Http2ClientConnection(HttpClientBase client, + ContextInternal context, + HostAndPort authority, VertxHttp2ConnectionHandler connHandler, ClientMetrics metrics) { super(context, connHandler); this.metrics = metrics; this.client = client; + this.authority = authority; + } + + @Override + public HostAndPort authority() { + return authority; } @Override @@ -80,6 +85,11 @@ public long concurrency() { return concurrency; } + @Override + public long activeStreams() { + return handler.connection().numActiveStreams(); + } + @Override boolean onGoAwaySent(GoAway goAway) { boolean goneAway = super.onGoAwaySent(goAway); @@ -147,6 +157,11 @@ public Future createStream(ContextInternal context) { } } + @Override + public Future createRequest(ContextInternal context) { + return ((HttpClientImpl)client).createRequest(this, context); + } + private StreamImpl createStream2(ContextInternal context) { return new StreamImpl(this, context, false); } @@ -227,14 +242,11 @@ static abstract class Stream extends VertxHttp2Stream { protected Handler exceptionHandler; protected Handler pushHandler; protected Handler closeHandler; - protected long writeWindow; - protected final long windowSize; Stream(Http2ClientConnection conn, ContextInternal context, boolean push) { super(conn, context); this.push = push; - this.windowSize = conn.getWindowSize(); } void onContinue() { @@ -263,9 +275,9 @@ void doWriteData(ByteBuf chunk, boolean end, Promise promise) { } @Override - void doWriteHeaders(Http2Headers headers, boolean end, Promise promise) { + void doWriteHeaders(Http2Headers headers, boolean end, boolean checkFlush, Promise promise) { isConnect = "CONNECT".contentEquals(headers.method()); - super.doWriteHeaders(headers, end, promise); + super.doWriteHeaders(headers, end, checkFlush, promise); } @Override @@ -343,7 +355,7 @@ void onHeaders(Http2Headers headers, StreamPriority streamPriority) { } private void removeStatusHeaders(Http2Headers headers) { - headers.remove(":status"); + headers.remove(HttpHeaders.PSEUDO_STATUS); } @Override @@ -435,8 +447,8 @@ public boolean writeQueueFull() { } @Override - public synchronized boolean isNotWritable() { - return writeWindow > windowSize; + public boolean isNotWritable() { + return !messageQueue.isWritable(); } @Override @@ -494,7 +506,11 @@ void handleReset(long errorCode) { } @Override - void handleWritabilityChanged(boolean writable) { + void handleWriteQueueDrained() { + Handler handler = drainHandler; + if (handler != null) { + context.dispatch(null, handler); + } } @Override @@ -534,9 +550,17 @@ void handleException(Throwable exception) { public Future writeHead(HttpRequestHead request, boolean chunked, ByteBuf buf, boolean end, StreamPriority priority, boolean connect) { priority(priority); PromiseInternal promise = context.promise(); - conn.context.emit(null, v -> { - writeHeaders(request, buf, end, priority, connect, promise); + messageQueue.write(new MessageWrite() { + @Override + public void write() { + writeHeaders(request, buf, end, priority, connect, promise); + } + @Override + public void cancel(Throwable cause) { + promise.fail(cause); + } }); + return promise.future(); } @@ -564,7 +588,7 @@ private void writeHeaders(HttpRequestHead request, ByteBuf buf, boolean end, Str headers.add(HttpUtils.toLowerCase(header.getKey()), header.getValue()); } } - if (conn.client.options().isTryUseCompression() && headers.get(HttpHeaderNames.ACCEPT_ENCODING) == null) { + if (conn.client.options().isDecompressionSupported() && headers.get(HttpHeaderNames.ACCEPT_ENCODING) == null) { headers.set(HttpHeaderNames.ACCEPT_ENCODING, Http1xClientConnection.determineCompressionAcceptEncoding()); } try { @@ -575,10 +599,10 @@ private void writeHeaders(HttpRequestHead request, ByteBuf buf, boolean end, Str return; } if (buf != null) { - doWriteHeaders(headers, false, null); + doWriteHeaders(headers, false, false, null); doWriteData(buf, e, promise); } else { - doWriteHeaders(headers, e, promise); + doWriteHeaders(headers, e, true, promise); } } @@ -611,29 +635,7 @@ private void createStream(HttpRequestHead head, Http2Headers headers) throws Htt public Future writeBuffer(ByteBuf buf, boolean end) { Promise promise = context.promise(); writeData(buf, end, promise); - Future fut = promise.future(); - if (buf != null) { - int size = buf.readableBytes(); - synchronized (this) { - writeWindow += size; - } - fut = fut.andThen(ar -> { - Handler drainHandler; - synchronized (this) { - boolean full = writeWindow > windowSize; - writeWindow -= size; - if (full && writeWindow <= windowSize) { - drainHandler = this.drainHandler; - } else { - drainHandler = null; - } - } - if (drainHandler != null) { - drainHandler.handle(null); - } - }); - } - return fut; + return promise.future(); } @Override @@ -647,7 +649,14 @@ public void doSetWriteQueueMaxSize(int size) { @Override public void reset(Throwable cause) { - long code = cause instanceof StreamResetException ? ((StreamResetException)cause).getCode() : 0; + long code; + if (cause instanceof StreamResetException) { + code = ((StreamResetException)cause).getCode(); + } else if (cause instanceof java.util.concurrent.TimeoutException) { + code = 0x08L; // CANCEL + } else { + code = 0L; + } conn.context.emit(code, this::writeReset); } @@ -665,20 +674,21 @@ protected void handleIdle(IdleStateEvent event) { } public static VertxHttp2ConnectionHandler createHttp2ConnectionHandler( - HttpClientImpl client, + HttpClientBase client, ClientMetrics metrics, - EventLoopContext context, + ContextInternal context, boolean upgrade, - Object socketMetric) { + Object socketMetric, + HostAndPort authority) { HttpClientOptions options = client.options(); HttpClientMetrics met = client.metrics(); VertxHttp2ConnectionHandler handler = new VertxHttp2ConnectionHandlerBuilder() .server(false) - .useDecompression(client.options().isTryUseCompression()) + .useDecompression(client.options().isDecompressionSupported()) .gracefulShutdownTimeoutMillis(0) // So client close tests don't hang 30 seconds - make this configurable later but requires HTTP/1 impl .initialSettings(client.options().getInitialSettings()) .connectionFactory(connHandler -> { - Http2ClientConnection conn = new Http2ClientConnection(client, context, connHandler, metrics); + Http2ClientConnection conn = new Http2ClientConnection(client, context, authority, connHandler, metrics); if (metrics != null) { Object m = socketMetric; conn.metric(m); diff --git a/src/main/java/io/vertx/core/http/impl/Http2ConnectionBase.java b/src/main/java/io/vertx/core/http/impl/Http2ConnectionBase.java index 6b5bb9b3da0..75d63ef1ed0 100644 --- a/src/main/java/io/vertx/core/http/impl/Http2ConnectionBase.java +++ b/src/main/java/io/vertx/core/http/impl/Http2ConnectionBase.java @@ -29,12 +29,13 @@ import io.vertx.core.Promise; import io.vertx.core.VertxException; import io.vertx.core.buffer.Buffer; +import io.vertx.core.buffer.impl.BufferInternal; import io.vertx.core.buffer.impl.VertxByteBufAllocator; import io.vertx.core.http.GoAway; import io.vertx.core.http.HttpClosedException; import io.vertx.core.http.HttpConnection; import io.vertx.core.http.StreamPriority; -import io.vertx.core.impl.EventLoopContext; +import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.future.PromiseInternal; import io.vertx.core.impl.VertxInternal; import io.vertx.core.impl.logging.Logger; @@ -75,7 +76,7 @@ private static ByteBuf safeBuffer(ByteBuf buf) { private int windowSize; private long maxConcurrentStreams; - public Http2ConnectionBase(EventLoopContext context, VertxHttp2ConnectionHandler handler) { + public Http2ConnectionBase(ContextInternal context, VertxHttp2ConnectionHandler handler) { super(context, handler.context()); this.handler = handler; this.handlerContext = chctx; @@ -94,11 +95,6 @@ public void handleClosed() { super.handleClosed(); } - @Override - protected void handleInterestedOpsChanged() { - // Handled by HTTP/2 - } - @Override protected void handleIdle(IdleStateEvent event) { super.handleIdle(event); @@ -291,7 +287,7 @@ public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int stream Http2Flags flags, ByteBuf payload) { VertxHttp2Stream stream = stream(streamId); if (stream != null) { - Buffer buff = Buffer.buffer(safeBuffer(payload)); + Buffer buff = BufferInternal.buffer(safeBuffer(payload)); stream.onCustomFrame(new HttpFrameImpl(frameType, flags.value(), buff)); } } @@ -309,7 +305,7 @@ public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int VertxHttp2Stream stream = stream(streamId); if (stream != null) { data = safeBuffer(data); - Buffer buff = Buffer.buffer(data); + Buffer buff = BufferInternal.buffer(data); stream.onData(buff); if (endOfStream) { stream.onEnd(); @@ -344,7 +340,7 @@ public HttpConnection goAway(long errorCode, int lastStreamId, Buffer debugData) if (lastStreamId < 0) { lastStreamId = handler.connection().remote().lastStreamCreated(); } - handler.writeGoAway(errorCode, lastStreamId, debugData != null ? debugData.getByteBuf() : Unpooled.EMPTY_BUFFER); + handler.writeGoAway(errorCode, lastStreamId, debugData != null ? ((BufferInternal)debugData).getByteBuf() : Unpooled.EMPTY_BUFFER); return this; } diff --git a/src/main/java/io/vertx/core/http/impl/Http2ServerConnection.java b/src/main/java/io/vertx/core/http/impl/Http2ServerConnection.java index f0dfd84ef38..ab12fe91864 100644 --- a/src/main/java/io/vertx/core/http/impl/Http2ServerConnection.java +++ b/src/main/java/io/vertx/core/http/impl/Http2ServerConnection.java @@ -15,12 +15,8 @@ import io.netty.handler.codec.compression.CompressionOptions; import io.netty.handler.codec.http.HttpContentCompressor; import io.netty.handler.codec.http.HttpHeaderNames; -import io.netty.handler.codec.http2.DefaultHttp2Headers; -import io.netty.handler.codec.http2.Http2CodecUtil; -import io.netty.handler.codec.http2.Http2Error; -import io.netty.handler.codec.http2.Http2Headers; +import io.netty.handler.codec.http2.*; import io.netty.handler.codec.http2.Http2Settings; -import io.netty.handler.codec.http2.Http2Stream; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.FutureListener; import io.vertx.core.Handler; @@ -28,11 +24,9 @@ import io.vertx.core.Promise; import io.vertx.core.http.*; import io.vertx.core.impl.ContextInternal; -import io.vertx.core.impl.EventLoopContext; +import io.vertx.core.net.HostAndPort; import io.vertx.core.spi.metrics.HttpServerMetrics; -import java.net.URI; -import java.net.URISyntaxException; import java.util.ArrayDeque; import java.util.function.Function; import java.util.function.Supplier; @@ -54,7 +48,7 @@ public class Http2ServerConnection extends Http2ConnectionBase implements HttpSe private VertxHttp2Stream upgraded; Http2ServerConnection( - EventLoopContext context, + ContextInternal context, Supplier streamContextSupplier, String serverOrigin, VertxHttp2ConnectionHandler connHandler, @@ -85,29 +79,28 @@ public HttpServerMetrics metrics() { return metrics; } - private static boolean isMalformedRequest(Http2Headers headers) { - if (headers.method() == null) { + private static boolean isMalformedRequest(Http2ServerStream request) { + if (request.method == null) { return true; } - String method = headers.method().toString(); - if (method.equals("CONNECT")) { - if (headers.scheme() != null || headers.path() != null || headers.authority() == null) { + + if (request.method == HttpMethod.CONNECT) { + if (request.scheme != null || request.uri != null || request.authority == null) { return true; } } else { - if (headers.method() == null || headers.scheme() == null || headers.path() == null || headers.path().length() == 0) { + if (request.scheme == null || request.uri == null || request.uri.length() == 0) { return true; } } - if (headers.authority() != null) { - URI uri; - try { - uri = new URI(null, headers.authority().toString(), null, null, null); - } catch (URISyntaxException e) { + if (request.hasAuthority) { + if (request.authority == null) { return true; } - if (uri.getRawUserInfo() != null) { - return true; + CharSequence hostHeader = request.headers.get(HttpHeaders.HOST); + if (hostHeader != null) { + HostAndPort host = HostAndPort.parseAuthority(hostHeader.toString(), -1); + return host == null || (!request.authority.host().equals(host.host()) || request.authority.port() != host.port()); } } return false; @@ -134,15 +127,36 @@ String determineContentEncoding(Http2Headers headers) { return null; } - private Http2ServerStream createStream(int streamId, Http2Headers headers, boolean streamEnded) { - Http2Stream stream = handler.connection().stream(streamId); - String contentEncoding = options.isCompressionSupported() ? determineContentEncoding(headers) : null; - Http2ServerStream vertxStream = new Http2ServerStream(this, streamContextSupplier.get(), headers, serverOrigin, options.getTracingPolicy(), streamEnded); - Http2ServerRequest request = new Http2ServerRequest(vertxStream, serverOrigin, headers, contentEncoding); + private Http2ServerStream createStream(Http2Headers headers, boolean streamEnded) { + CharSequence schemeHeader = headers.getAndRemove(HttpHeaders.PSEUDO_SCHEME); + HostAndPort authority = null; + String authorityHeaderAsString; + CharSequence authorityHeader = headers.getAndRemove(HttpHeaders.PSEUDO_AUTHORITY); + if (authorityHeader != null) { + authorityHeaderAsString = authorityHeader.toString(); + authority = HostAndPort.parseAuthority(authorityHeaderAsString, -1); + } + CharSequence pathHeader = headers.getAndRemove(HttpHeaders.PSEUDO_PATH); + CharSequence methodHeader = headers.getAndRemove(HttpHeaders.PSEUDO_METHOD); + return new Http2ServerStream( + this, + streamContextSupplier.get(), + headers, + schemeHeader != null ? schemeHeader.toString() : null, + authorityHeader != null, + authority, + methodHeader != null ? HttpMethod.valueOf(methodHeader.toString()) : null, + pathHeader != null ? pathHeader.toString() : null, + options.getTracingPolicy(), streamEnded); + } + + private void initStream(int streamId, Http2ServerStream vertxStream) { + String contentEncoding = options.isCompressionSupported() ? determineContentEncoding(vertxStream.headers) : null; + Http2ServerRequest request = new Http2ServerRequest(vertxStream, serverOrigin, vertxStream.headers, contentEncoding); vertxStream.request = request; vertxStream.isConnect = request.method() == HttpMethod.CONNECT; + Http2Stream stream = handler.connection().stream(streamId); vertxStream.init(stream); - return vertxStream; } VertxHttp2Stream stream(int id) { @@ -155,18 +169,19 @@ VertxHttp2Stream stream(int id) { @Override protected synchronized void onHeadersRead(int streamId, Http2Headers headers, StreamPriority streamPriority, boolean endOfStream) { - VertxHttp2Stream stream = stream(streamId); + Http2ServerStream stream = (Http2ServerStream) stream(streamId); if (stream == null) { - if (isMalformedRequest(headers)) { - handler.writeReset(streamId, Http2Error.PROTOCOL_ERROR.code()); - return; - } if (streamId == 1 && handler.upgraded) { - stream = createStream(streamId, headers, true); + stream = createStream(headers, true); upgraded = stream; } else { - stream = createStream(streamId, headers, endOfStream); + stream = createStream(headers, endOfStream); + } + if (isMalformedRequest(stream)) { + handler.writeReset(streamId, Http2Error.PROTOCOL_ERROR.code()); + return; } + initStream(streamId, stream); stream.onHeaders(headers, streamPriority); } else { // Http server request trailer - not implemented yet (in api) @@ -176,22 +191,24 @@ protected synchronized void onHeadersRead(int streamId, Http2Headers headers, St } } - void sendPush(int streamId, String host, HttpMethod method, MultiMap headers, String path, StreamPriority streamPriority, Promise promise) { + void sendPush(int streamId, HostAndPort authority, HttpMethod method, MultiMap headers, String path, StreamPriority streamPriority, Promise promise) { EventLoop eventLoop = context.nettyEventLoop(); if (eventLoop.inEventLoop()) { - doSendPush(streamId, host, method, headers, path, streamPriority, promise); + doSendPush(streamId, authority, method, headers, path, streamPriority, promise); } else { - eventLoop.execute(() -> doSendPush(streamId, host, method, headers, path, streamPriority, promise)); + eventLoop.execute(() -> doSendPush(streamId, authority, method, headers, path, streamPriority, promise)); } } - private synchronized void doSendPush(int streamId, String host, HttpMethod method, MultiMap headers, String path, StreamPriority streamPriority, Promise promise) { + private synchronized void doSendPush(int streamId, HostAndPort authority, HttpMethod method, MultiMap headers, String path, StreamPriority streamPriority, Promise promise) { + boolean ssl = isSsl(); Http2Headers headers_ = new DefaultHttp2Headers(); headers_.method(method.name()); headers_.path(path); - headers_.scheme(isSsl() ? "https" : "http"); - if (host != null) { - headers_.authority(host); + headers_.scheme(ssl ? "https" : "http"); + if (authority != null) { + String s = (ssl && authority.port() == 443) || (!ssl && authority.port() == 80) || authority.port() <= 0 ? authority.host() : authority.host() + ':' + authority.port(); + headers_.authority(s); } if (headers != null) { headers.forEach(header -> headers_.add(header.getKey(), header.getValue())); diff --git a/src/main/java/io/vertx/core/http/impl/Http2ServerRequest.java b/src/main/java/io/vertx/core/http/impl/Http2ServerRequest.java index 2e2546daae8..4986b12599a 100644 --- a/src/main/java/io/vertx/core/http/impl/Http2ServerRequest.java +++ b/src/main/java/io/vertx/core/http/impl/Http2ServerRequest.java @@ -27,6 +27,7 @@ import io.vertx.core.Handler; import io.vertx.core.MultiMap; import io.vertx.core.buffer.Buffer; +import io.vertx.core.buffer.impl.BufferInternal; import io.vertx.core.http.Cookie; import io.vertx.core.http.HttpConnection; import io.vertx.core.http.HttpMethod; @@ -41,9 +42,9 @@ import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; +import io.vertx.core.net.HostAndPort; import io.vertx.core.net.NetSocket; import io.vertx.core.net.SocketAddress; -import io.vertx.core.tracing.TracingPolicy; import javax.net.ssl.SSLPeerUnverifiedException; import javax.security.cert.X509Certificate; @@ -65,7 +66,6 @@ public class Http2ServerRequest extends HttpServerRequestInternal implements Htt protected final Http2ServerResponse response; private final String serverOrigin; private final MultiMap headersMap; - private final String scheme; // Accessed on context thread private Charset paramsCharset = StandardCharsets.UTF_8; @@ -83,17 +83,10 @@ public class Http2ServerRequest extends HttpServerRequestInternal implements Htt String serverOrigin, Http2Headers headers, String contentEncoding) { - String scheme = headers.get(":scheme") != null ? headers.get(":scheme").toString() : null; - headers.remove(":method"); - headers.remove(":scheme"); - headers.remove(":path"); - headers.remove(":authority"); - this.context = stream.context; this.stream = stream; this.response = new Http2ServerResponse(stream.conn, stream, false, contentEncoding); this.serverOrigin = serverOrigin; - this.scheme = scheme; this.headersMap = new Http2HeadersAdaptor(headers); } @@ -152,7 +145,7 @@ public void handleCustomFrame(HttpFrame frame) { public void handleData(Buffer data) { if (postRequestDecoder != null) { try { - postRequestDecoder.offer(new DefaultHttpContent(data.getByteBuf())); + postRequestDecoder.offer(new DefaultHttpContent(((BufferInternal)data).getByteBuf())); } catch (Exception e) { handleException(e); } @@ -330,12 +323,12 @@ public String query() { @Override public String scheme() { - return scheme; + return stream.scheme; } @Override - public String host() { - return stream.host; + public @Nullable HostAndPort authority() { + return stream.authority; } @Override @@ -385,7 +378,7 @@ public X509Certificate[] peerCertificateChain() throws SSLPeerUnverifiedExceptio @Override public SocketAddress remoteAddress() { - return stream.conn.remoteAddress(); + return super.remoteAddress(); } @Override diff --git a/src/main/java/io/vertx/core/http/impl/Http2ServerResponse.java b/src/main/java/io/vertx/core/http/impl/Http2ServerResponse.java index 3e5a4665606..760413d8e91 100644 --- a/src/main/java/io/vertx/core/http/impl/Http2ServerResponse.java +++ b/src/main/java/io/vertx/core/http/impl/Http2ServerResponse.java @@ -25,6 +25,7 @@ import io.vertx.core.MultiMap; import io.vertx.core.Promise; import io.vertx.core.buffer.Buffer; +import io.vertx.core.buffer.impl.BufferInternal; import io.vertx.core.http.Cookie; import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.HttpMethod; @@ -33,6 +34,7 @@ import io.vertx.core.http.StreamResetException; import io.vertx.core.http.impl.headers.Http2HeadersAdaptor; import io.vertx.core.impl.future.PromiseInternal; +import io.vertx.core.net.HostAndPort; import io.vertx.core.net.NetSocket; import io.vertx.core.spi.observability.HttpResponse; import io.vertx.core.streams.ReadStream; @@ -312,12 +314,13 @@ public HttpServerResponse endHandler(@Nullable Handler handler) { } @Override - public HttpServerResponse writeContinue() { + public Future writeContinue() { + Promise promise = stream.context.promise(); synchronized (conn) { checkHeadWritten(); - stream.writeHeaders(new DefaultHttp2Headers().status(HttpResponseStatus.CONTINUE.codeAsText()), false, null); - return this; + stream.writeHeaders(new DefaultHttp2Headers().status(HttpResponseStatus.CONTINUE.codeAsText()), true, false, true, promise); } + return promise.future(); } @Override @@ -331,24 +334,24 @@ public Future writeEarlyHints(MultiMap headers) { synchronized (conn) { checkHeadWritten(); } - stream.writeHeaders(http2Headers, false, promise); + stream.writeHeaders(http2Headers, true, false, true, promise); return promise.future(); } @Override public Future write(Buffer chunk) { - ByteBuf buf = chunk.getByteBuf(); + ByteBuf buf = ((BufferInternal)chunk).getByteBuf(); return write(buf, false); } @Override public Future write(String chunk, String enc) { - return write(Buffer.buffer(chunk, enc).getByteBuf(), false); + return write(BufferInternal.buffer(chunk, enc).getByteBuf(), false); } @Override public Future write(String chunk) { - return write(Buffer.buffer(chunk).getByteBuf(), false); + return write(BufferInternal.buffer(chunk).getByteBuf(), false); } @Override @@ -363,7 +366,7 @@ public Future end(String chunk, String enc) { @Override public Future end(Buffer chunk) { - return write(chunk.getByteBuf(), true); + return write(((BufferInternal)chunk).getByteBuf(), true); } @Override @@ -378,7 +381,6 @@ Future netSocket() { if (!checkSendHeaders(false)) { netSocket = stream.context.failedFuture("Response for CONNECT already sent"); } else { - ctx.flush(); HttpNetSocket ns = HttpNetSocket.netSocket(conn, stream.context, (ReadStream) stream.request, this); netSocket = Future.succeededFuture(ns); } @@ -405,7 +407,7 @@ Future write(ByteBuf chunk, boolean end) { if (end && !headWritten && needsContentLengthHeader()) { headers().set(HttpHeaderNames.CONTENT_LENGTH, String.valueOf(chunk.readableBytes())); } - boolean sent = checkSendHeaders(end && !hasBody && trailers == null); + boolean sent = checkSendHeaders(end && !hasBody && trailers == null, !hasBody); if (hasBody || (!sent && end)) { Promise p = stream.context.promise(); fut = p.future(); @@ -414,7 +416,7 @@ Future write(ByteBuf chunk, boolean end) { fut = stream.context.succeededFuture(); } if (end && trailers != null) { - stream.writeHeaders(trailers, true, null); + stream.writeHeaders(trailers, false, true, true, null); } bodyEndHandler = this.bodyEndHandler; endHandler = this.endHandler; @@ -435,6 +437,10 @@ private boolean needsContentLengthHeader() { } private boolean checkSendHeaders(boolean end) { + return checkSendHeaders(end, true); + } + + private boolean checkSendHeaders(boolean end, boolean checkFlush) { if (!headWritten) { if (headersEndHandler != null) { headersEndHandler.handle(null); @@ -444,10 +450,7 @@ private boolean checkSendHeaders(boolean end) { } prepareHeaders(); headWritten = true; - stream.writeHeaders(headers, end, null); - if (end) { - ctx.flush(); - } + stream.writeHeaders(headers, true, end, checkFlush, null); return true; } else { return false; @@ -455,7 +458,7 @@ private boolean checkSendHeaders(boolean end) { } private void prepareHeaders() { - headers.status(Integer.toString(status.code())); // Could be optimized for usual case ? + headers.status(status.codeAsText()); // Could be optimized for usual case ? if (contentEncoding != null && headers.get(HttpHeaderNames.CONTENT_ENCODING) == null) { headers.set(HttpHeaderNames.CONTENT_ENCODING, contentEncoding); } @@ -480,14 +483,14 @@ private void setCookies() { } @Override - public HttpServerResponse writeCustomFrame(int type, int flags, Buffer payload) { + public Future writeCustomFrame(int type, int flags, Buffer payload) { + Promise promise = stream.context.promise(); synchronized (conn) { checkValid(); checkSendHeaders(false); - stream.writeFrame(type, flags, payload.getByteBuf()); - ctx.flush(); - return this; + stream.writeFrame(type, flags, ((BufferInternal)payload).getByteBuf(), promise); } + return promise.future(); } private void checkValid() { @@ -496,10 +499,15 @@ private void checkValid() { } } - void handlerWritabilityChanged(boolean writable) { - if (!ended && writable && drainHandler != null) { - drainHandler.handle(null); + void handleWriteQueueDrained() { + Handler handler; + synchronized (conn) { + handler = drainHandler; + if (ended || handler == null) { + return; + } } + handler.handle(null); } @Override @@ -551,15 +559,10 @@ public Future sendFile(String filename, long offset, long length) { checkSendHeaders(false); return file .pipeTo(this) - .eventually(v -> file.close()); + .eventually(() -> file.close()); }); } - @Override - public void close() { - conn.close(); - } - @Override public boolean ended() { synchronized (conn) { @@ -614,18 +617,38 @@ public boolean reset(long code) { } @Override - public Future push(HttpMethod method, String host, String path, MultiMap headers) { + public Future push(HttpMethod method, HostAndPort authority, String path, MultiMap headers) { if (push) { throw new IllegalStateException("A push response cannot promise another push"); } - if (host == null) { - host = stream.host; + if (authority == null) { + authority = stream.authority; + } + synchronized (conn) { + checkValid(); + } + Promise promise = stream.context.promise(); + conn.sendPush(stream.id(), authority, method, headers, path, stream.priority(), promise); + return promise.future(); + } + + @Override + public Future push(HttpMethod method, String authority, String path, MultiMap headers) { + if (push) { + throw new IllegalStateException("A push response cannot promise another push"); + } + HostAndPort hostAndPort = null; + if (authority != null) { + hostAndPort = HostAndPort.parseAuthority(authority, -1); + } + if (hostAndPort == null) { + hostAndPort = stream.authority; } synchronized (conn) { checkValid(); } Promise promise = stream.context.promise(); - conn.sendPush(stream.id(), host, method, headers, path, stream.priority(), promise); + conn.sendPush(stream.id(), hostAndPort, method, headers, path, stream.priority(), promise); return promise.future(); } diff --git a/src/main/java/io/vertx/core/http/impl/Http2ServerStream.java b/src/main/java/io/vertx/core/http/impl/Http2ServerStream.java index e8ed6255366..fc1f40fd0e2 100644 --- a/src/main/java/io/vertx/core/http/impl/Http2ServerStream.java +++ b/src/main/java/io/vertx/core/http/impl/Http2ServerStream.java @@ -22,6 +22,8 @@ import io.vertx.core.http.StreamPriority; import io.vertx.core.http.impl.headers.Http2HeadersAdaptor; import io.vertx.core.impl.ContextInternal; +import io.vertx.core.net.HostAndPort; +import io.vertx.core.net.impl.HostAndPortImpl; import io.vertx.core.spi.metrics.HttpServerMetrics; import io.vertx.core.spi.metrics.Metrics; import io.vertx.core.spi.observability.HttpRequest; @@ -34,9 +36,11 @@ class Http2ServerStream extends VertxHttp2Stream { protected final Http2Headers headers; + protected final String scheme; protected final HttpMethod method; protected final String uri; - protected final String host; + protected final boolean hasAuthority; + protected final HostAndPort authority; private final TracingPolicy tracingPolicy; private Object metric; private Object trace; @@ -56,24 +60,31 @@ class Http2ServerStream extends VertxHttp2Stream { this.headers = null; this.method = method; this.uri = uri; - this.host = null; + this.scheme = null; + this.hasAuthority = false; + this.authority = null; this.tracingPolicy = tracingPolicy; this.halfClosedRemote = halfClosedRemote; } - Http2ServerStream(Http2ServerConnection conn, ContextInternal context, Http2Headers headers, String serverOrigin, TracingPolicy tracingPolicy, boolean halfClosedRemote) { + Http2ServerStream(Http2ServerConnection conn, + ContextInternal context, + Http2Headers headers, + String scheme, + boolean hasAuthority, + HostAndPort authority, + HttpMethod method, + String uri, + TracingPolicy tracingPolicy, + boolean halfClosedRemote) { super(conn, context); - String host = headers.get(":authority") != null ? headers.get(":authority").toString() : null; - if (host == null) { - int idx = serverOrigin.indexOf("://"); - host = serverOrigin.substring(idx + 3); - } - + this.scheme = scheme; this.headers = headers; - this.host = host; - this.uri = headers.get(":path") != null ? headers.get(":path").toString() : null; - this.method = headers.get(":method") != null ? HttpMethod.valueOf(headers.get(":method").toString()) : null; + this.hasAuthority = hasAuthority; + this.authority = authority; + this.uri = uri; + this.method = method; this.tracingPolicy = tracingPolicy; this.halfClosedRemote = halfClosedRemote; } @@ -123,14 +134,14 @@ void onEnd(MultiMap trailers) { } @Override - void doWriteHeaders(Http2Headers headers, boolean end, Promise promise) { + void doWriteHeaders(Http2Headers headers, boolean end, boolean checkFlush, Promise promise) { if (Metrics.METRICS_ENABLED && !end) { HttpServerMetrics metrics = conn.metrics(); if (metrics != null) { metrics.responseBegin(metric, request.response()); } } - super.doWriteHeaders(headers, end, promise); + super.doWriteHeaders(headers, end, checkFlush, promise); } @Override @@ -141,8 +152,8 @@ protected void doWriteReset(long code) { } @Override - void handleWritabilityChanged(boolean writable) { - request.response().handlerWritabilityChanged(writable); + void handleWriteQueueDrained() { + request.response().handleWriteQueueDrained(); } public HttpMethod method() { diff --git a/src/main/java/io/vertx/core/http/impl/Http2UpgradeClientConnection.java b/src/main/java/io/vertx/core/http/impl/Http2UpgradeClientConnection.java index 135d18a021a..1ac47f62926 100644 --- a/src/main/java/io/vertx/core/http/impl/Http2UpgradeClientConnection.java +++ b/src/main/java/io/vertx/core/http/impl/Http2UpgradeClientConnection.java @@ -25,11 +25,11 @@ import io.vertx.core.buffer.Buffer; import io.vertx.core.http.*; import io.vertx.core.http.HttpVersion; -import io.vertx.core.impl.EventLoopContext; import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.future.PromiseInternal; import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; +import io.vertx.core.net.HostAndPort; import io.vertx.core.net.SocketAddress; import io.vertx.core.streams.WriteStream; @@ -51,7 +51,7 @@ public class Http2UpgradeClientConnection implements HttpClientConnection { private static final Logger log = LoggerFactory.getLogger(Http2UpgradeClientConnection.class); - private HttpClientImpl client; + private HttpClientBase client; private HttpClientConnection current; private boolean upgradeProcessed; @@ -64,7 +64,7 @@ public class Http2UpgradeClientConnection implements HttpClientConnection { private Handler concurrencyChangeHandler; private Handler remoteSettingsHandler; - Http2UpgradeClientConnection(HttpClientImpl client, Http1xClientConnection connection) { + Http2UpgradeClientConnection(HttpClientBase client, Http1xClientConnection connection) { this.client = client; this.current = connection; } @@ -73,11 +73,21 @@ public HttpClientConnection unwrap() { return current; } + @Override + public HostAndPort authority() { + return current.authority(); + } + @Override public long concurrency() { return upgradeProcessed ? current.concurrency() : 1L; } + @Override + public long activeStreams() { + return current.concurrency(); + } + @Override public ChannelHandlerContext channelHandlerContext() { return current.channelHandlerContext(); @@ -154,8 +164,8 @@ public Future writeBuffer(ByteBuf buf, boolean end) { } @Override - public void writeFrame(int type, int flags, ByteBuf payload) { - delegate.writeFrame(type, flags, payload); + public Future writeFrame(int type, int flags, ByteBuf payload) { + return delegate.writeFrame(type, flags, payload); } @Override @@ -348,7 +358,7 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception public void upgradeTo(ChannelHandlerContext ctx, FullHttpResponse upgradeResponse) throws Exception { // Now we need to upgrade this to an HTTP2 - VertxHttp2ConnectionHandler handler = Http2ClientConnection.createHttp2ConnectionHandler(upgradedConnection.client, upgradingConnection.metrics, (EventLoopContext) upgradingConnection.getContext(), true, upgradedConnection.current.metric()); + VertxHttp2ConnectionHandler handler = Http2ClientConnection.createHttp2ConnectionHandler(upgradedConnection.client, upgradingConnection.metrics, upgradingConnection.getContext(), true, upgradedConnection.current.metric(), upgradedConnection.current.authority()); upgradingConnection.channel().pipeline().addLast(handler); handler.connectFuture().addListener(future -> { if (!future.isSuccess()) { @@ -699,11 +709,11 @@ public Future writeBuffer(ByteBuf buf, boolean end) { } @Override - public void writeFrame(int type, int flags, ByteBuf payload) { + public Future writeFrame(int type, int flags, ByteBuf payload) { if (upgradedStream != null) { - upgradedStream.writeFrame(type, flags, payload); + return upgradedStream.writeFrame(type, flags, payload); } else { - upgradingStream.writeFrame(type, flags, payload); + return upgradingStream.writeFrame(type, flags, payload); } } @@ -784,6 +794,11 @@ public Future createStream(ContextInternal context) { } } + @Override + public Future createRequest(ContextInternal context) { + return ((HttpClientImpl)client).createRequest(this, context); + } + @Override public ContextInternal getContext() { return current.getContext(); diff --git a/src/main/java/io/vertx/core/http/impl/HttpChannelConnector.java b/src/main/java/io/vertx/core/http/impl/HttpChannelConnector.java index eb611155f4c..54d5d4b24df 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpChannelConnector.java +++ b/src/main/java/io/vertx/core/http/impl/HttpChannelConnector.java @@ -14,7 +14,8 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelPipeline; -import io.netty.handler.codec.http.*; +import io.netty.handler.codec.http.HttpClientCodec; +import io.netty.handler.codec.http.HttpContentDecompressor; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.ssl.SslHandler; import io.netty.handler.timeout.IdleStateHandler; @@ -23,12 +24,9 @@ import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.HttpVersion; -import io.vertx.core.impl.EventLoopContext; import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.future.PromiseInternal; -import io.vertx.core.net.NetSocket; -import io.vertx.core.net.ProxyOptions; -import io.vertx.core.net.SocketAddress; +import io.vertx.core.net.*; import io.vertx.core.net.impl.NetClientInternal; import io.vertx.core.net.impl.NetSocketImpl; import io.vertx.core.net.impl.VertxHandler; @@ -48,35 +46,38 @@ */ public class HttpChannelConnector { - private final HttpClientImpl client; + private final HttpClientBase client; private final NetClientInternal netClient; private final HttpClientOptions options; + private final ClientSSLOptions sslOptions; private final ProxyOptions proxyOptions; private final ClientMetrics metrics; private final boolean ssl; private final boolean useAlpn; private final HttpVersion version; - private final SocketAddress peerAddress; + private final HostAndPort authority; private final SocketAddress server; - public HttpChannelConnector(HttpClientImpl client, + public HttpChannelConnector(HttpClientBase client, NetClientInternal netClient, + ClientSSLOptions sslOptions, ProxyOptions proxyOptions, ClientMetrics metrics, HttpVersion version, boolean ssl, boolean useAlpn, - SocketAddress peerAddress, + HostAndPort authority, SocketAddress server) { this.client = client; this.netClient = netClient; this.metrics = metrics; this.options = client.options(); this.proxyOptions = proxyOptions; + this.sslOptions = sslOptions; this.ssl = ssl; this.useAlpn = useAlpn; this.version = version; - this.peerAddress = peerAddress; + this.authority = authority; this.server = server; } @@ -84,11 +85,29 @@ public SocketAddress server() { return server; } - private void connect(EventLoopContext context, Promise promise) { - netClient.connectInternal(proxyOptions, server, peerAddress, this.options.isForceSni() ? peerAddress.host() : null, ssl, useAlpn, false, promise, context, 0); + private void connect(ContextInternal context, Promise promise) { + ConnectOptions connectOptions = new ConnectOptions(); + connectOptions.setRemoteAddress(server); + if (authority != null) { + connectOptions.setHost(authority.host()); + connectOptions.setPort(authority.port()); + if (ssl && options.isForceSni()) { + connectOptions.setSniServerName(authority.host()); + } + } + connectOptions.setSsl(ssl); + if (ssl) { + if (sslOptions != null) { + connectOptions.setSslOptions(sslOptions.copy().setUseAlpn(useAlpn)); + } else { + // should not be possible + } + } + connectOptions.setProxyOptions(proxyOptions); + netClient.connectInternal(connectOptions, promise, context); } - public Future wrap(EventLoopContext context, NetSocket so_) { + public Future wrap(ContextInternal context, NetSocket so_) { NetSocketImpl so = (NetSocketImpl) so_; Object metric = so.metric(); PromiseInternal promise = context.promise(); @@ -139,7 +158,7 @@ public Future wrap(EventLoopContext context, NetSocket so_ return promise.future(); } - public Future httpConnect(EventLoopContext context) { + public Future httpConnect(ContextInternal context) { Promise promise = context.promise(); Future future = promise.future(); // We perform the compose operation before calling connect to be sure that the composition happens @@ -175,7 +194,7 @@ private void applyHttp1xConnectionOptions(ChannelPipeline pipeline) { false, !HttpHeaders.DISABLE_HTTP_HEADERS_VALIDATION, options.getDecoderInitialBufferSize())); - if (options.isTryUseCompression()) { + if (options.isDecompressionSupported()) { pipeline.addLast("inflater", new HttpContentDecompressor(false)); } } @@ -190,7 +209,7 @@ private void http1xConnected(HttpVersion version, boolean upgrade = version == HttpVersion.HTTP_2 && options.isHttp2ClearTextUpgrade(); VertxHandler clientHandler = VertxHandler.create(chctx -> { HttpClientMetrics met = client.metrics(); - Http1xClientConnection conn = new Http1xClientConnection(upgrade ? HttpVersion.HTTP_1_1 : version, client, chctx, ssl, server, context, this.metrics); + Http1xClientConnection conn = new Http1xClientConnection(upgrade ? HttpVersion.HTTP_1_1 : version, client, chctx, ssl, server, authority, context, this.metrics); if (met != null) { conn.metric(socketMetric); met.endpointConnected(metrics); @@ -231,13 +250,13 @@ private void http1xConnected(HttpVersion version, ch.pipeline().addLast("handler", clientHandler); } - private void http2Connected(EventLoopContext context, + private void http2Connected(ContextInternal context, Object metric, Channel ch, PromiseInternal promise) { VertxHttp2ConnectionHandler clientHandler; try { - clientHandler = Http2ClientConnection.createHttp2ConnectionHandler(client, metrics, context, false, metric); + clientHandler = Http2ClientConnection.createHttp2ConnectionHandler(client, metrics, context, false, metric, authority); ch.pipeline().addLast("handler", clientHandler); ch.flush(); } catch (Exception e) { diff --git a/src/main/java/io/vertx/core/http/impl/HttpClientBase.java b/src/main/java/io/vertx/core/http/impl/HttpClientBase.java new file mode 100644 index 00000000000..f7e0eb3c437 --- /dev/null +++ b/src/main/java/io/vertx/core/http/impl/HttpClientBase.java @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2011-2022 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ + +package io.vertx.core.http.impl; + +import io.vertx.core.*; +import io.vertx.core.http.*; +import io.vertx.core.impl.CloseSequence; +import io.vertx.core.impl.ContextInternal; +import io.vertx.core.impl.VertxInternal; +import io.vertx.core.net.*; +import io.vertx.core.net.impl.NetClientBuilder; +import io.vertx.core.net.impl.NetClientInternal; +import io.vertx.core.net.impl.ProxyFilter; +import io.vertx.core.spi.metrics.HttpClientMetrics; +import io.vertx.core.spi.metrics.Metrics; +import io.vertx.core.spi.metrics.MetricsProvider; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * This class is thread-safe. + * + * @author Tim Fox + */ +public class HttpClientBase implements MetricsProvider, Closeable { + + protected final VertxInternal vertx; + final HttpClientOptions options; + protected final NetClientInternal netClient; + protected final List alpnVersions; + protected final HttpClientMetrics metrics; + protected final CloseSequence closeSequence; + private volatile ClientSSLOptions defaultSslOptions; + private long closeTimeout = 0L; + private TimeUnit closeTimeoutUnit = TimeUnit.SECONDS; + private Predicate proxyFilter; + + public HttpClientBase(VertxInternal vertx, HttpClientOptions options) { + List alpnVersions = options.getAlpnVersions(); + if (alpnVersions == null || alpnVersions.isEmpty()) { + switch (options.getProtocolVersion()) { + case HTTP_2: + alpnVersions = Arrays.asList(HttpVersion.HTTP_2, HttpVersion.HTTP_1_1); + break; + default: + alpnVersions = Collections.singletonList(options.getProtocolVersion()); + break; + } + } else { + alpnVersions = new ArrayList<>(alpnVersions); + } + ClientSSLOptions sslOptions = options + .getSslOptions(); + if (sslOptions != null) { + sslOptions = sslOptions + .copy() + .setHostnameVerificationAlgorithm(options.isVerifyHost() ? "HTTPS" : "") + .setApplicationLayerProtocols(alpnVersions.stream().map(HttpVersion::alpnName).collect(Collectors.toList())); + } + this.alpnVersions = alpnVersions; + this.vertx = vertx; + this.metrics = vertx.metricsSPI() != null ? vertx.metricsSPI().createHttpClientMetrics(options) : null; + this.options = new HttpClientOptions(options); + this.defaultSslOptions = sslOptions; + this.closeSequence = new CloseSequence(this::doClose, this::doShutdown); + if (!options.isKeepAlive() && options.isPipelining()) { + throw new IllegalStateException("Cannot have pipelining with no keep alive"); + } + this.proxyFilter = options.getNonProxyHosts() != null ? ProxyFilter.nonProxyHosts(options.getNonProxyHosts()) : ProxyFilter.DEFAULT_PROXY_FILTER; + this.netClient = new NetClientBuilder(vertx, new NetClientOptions(options).setProxyOptions(null)).metrics(metrics).build(); + } + + public NetClientInternal netClient() { + return netClient; + } + + public Future closeFuture() { + return closeSequence.future(); + } + + public void close(Promise completion) { + closeSequence.close(completion); + } + + protected int getPort(RequestOptions request) { + Integer port = request.getPort(); + if (port != null) { + return port; + } + SocketAddress server = (SocketAddress) request.getServer(); + if (server != null && server.isInetSocket()) { + return server.port(); + } + return options.getDefaultPort(); + } + + private ProxyOptions getProxyOptions(ProxyOptions proxyOptions) { + if (proxyOptions == null) { + proxyOptions = options.getProxyOptions(); + } + return proxyOptions; + } + + protected String getHost(RequestOptions request) { + String host = request.getHost(); + if (host != null) { + return host; + } + SocketAddress server = (SocketAddress) request.getServer(); + if (server != null && server.isInetSocket()) { + return server.host(); + } + return options.getDefaultHost(); + } + + protected ProxyOptions computeProxyOptions(ProxyOptions proxyOptions, SocketAddress addr) { + proxyOptions = getProxyOptions(proxyOptions); + if (proxyFilter != null) { + if (!proxyFilter.test(addr)) { + proxyOptions = null; + } + } + return proxyOptions; + } + + protected ClientSSLOptions sslOptions(RequestOptions requestOptions) { + ClientSSLOptions sslOptions = requestOptions.getSslOptions(); + if (sslOptions != null) { + sslOptions = new ClientSSLOptions(sslOptions); + } else { + sslOptions = defaultSslOptions; + } + return sslOptions; + } + + HttpClientMetrics metrics() { + return metrics; + } + + /** + * Connect to a server. + */ + public Future connect(SocketAddress server) { + return connect(server, null); + } + + /** + * Connect to a server. + */ + public Future connect(SocketAddress server, HostAndPort peer) { + ContextInternal context = vertx.getOrCreateContext(); + HttpChannelConnector connector = new HttpChannelConnector(this, netClient, defaultSslOptions, null, null, options.getProtocolVersion(), options.isSsl(), options.isUseAlpn(), peer, server); + return connector.httpConnect(context); + } + + + protected void doShutdown(Promise p) { + netClient.shutdown(closeTimeout, closeTimeoutUnit).onComplete(p); + } + + protected void doClose(Promise p) { + netClient.close().onComplete(p); + } + + public Future close(long timeout, TimeUnit timeUnit) { + this.closeTimeout = timeout; + this.closeTimeoutUnit = timeUnit; + return closeSequence.close(); + } + + @Override + public boolean isMetricsEnabled() { + return getMetrics() != null; + } + + @Override + public Metrics getMetrics() { + return metrics; + } + + public Future updateSSLOptions(ClientSSLOptions options, boolean force) { + defaultSslOptions = options + .copy() + .setHostnameVerificationAlgorithm(this.options.isVerifyHost() ? "HTTPS" : "") + .setApplicationLayerProtocols(alpnVersions.stream().map(HttpVersion::alpnName).collect(Collectors.toList()));; + return Future.succeededFuture(true); + } + + public HttpClientBase proxyFilter(Predicate filter) { + proxyFilter = filter; + return this; + } + + public HttpClientOptions options() { + return options; + } + + public VertxInternal vertx() { + return vertx; + } + + protected void checkClosed() { + if (closeSequence.started()) { + throw new IllegalStateException("Client is closed"); + } + } +} diff --git a/src/main/java/io/vertx/core/http/impl/HttpClientBuilderInternal.java b/src/main/java/io/vertx/core/http/impl/HttpClientBuilderInternal.java new file mode 100644 index 00000000000..56474c1a6ac --- /dev/null +++ b/src/main/java/io/vertx/core/http/impl/HttpClientBuilderInternal.java @@ -0,0 +1,130 @@ +package io.vertx.core.http.impl; + + +import io.vertx.core.Closeable; +import io.vertx.core.Future; +import io.vertx.core.Handler; +import io.vertx.core.http.*; +import io.vertx.core.impl.CloseFuture; +import io.vertx.core.impl.ContextInternal; +import io.vertx.core.impl.VertxInternal; +import io.vertx.core.loadbalancing.LoadBalancer; +import io.vertx.core.net.AddressResolver; +import io.vertx.core.net.impl.resolver.EndpointResolverImpl; +import io.vertx.core.spi.resolver.endpoint.EndpointResolver; + +import java.util.function.Function; + +public final class HttpClientBuilderInternal implements HttpClientBuilder { + + private final VertxInternal vertx; + private HttpClientOptions clientOptions; + private PoolOptions poolOptions; + private Handler connectHandler; + private Function> redirectHandler; + private io.vertx.core.net.AddressResolver addressResolver; + private LoadBalancer loadBalancer = null; + private EndpointResolver endpointResolver; + + public HttpClientBuilderInternal(VertxInternal vertx) { + this.vertx = vertx; + } + + @Override + public HttpClientBuilder with(HttpClientOptions options) { + this.clientOptions = options; + return this; + } + + @Override + public HttpClientBuilder with(PoolOptions options) { + this.poolOptions = options; + return this; + } + + @Override + public HttpClientBuilder withConnectHandler(Handler handler) { + this.connectHandler = handler; + return this; + } + + @Override + public HttpClientBuilder withRedirectHandler(Function> handler) { + this.redirectHandler = handler; + return this; + } + + @Override + public HttpClientBuilder withAddressResolver(io.vertx.core.net.AddressResolver addressResolver) { + this.addressResolver = addressResolver; + return this; + } + + @Override + public HttpClientBuilder withLoadBalancer(LoadBalancer loadBalancer) { + this.loadBalancer = loadBalancer; + return this; + } + + public HttpClientBuilderInternal withEndpointResolver(EndpointResolver resolver) { + endpointResolver = resolver; + return this; + } + + private CloseFuture resolveCloseFuture() { + ContextInternal context = vertx.getContext(); + return context != null ? context.closeFuture() : vertx.closeFuture(); + } + + private EndpointResolver endpointResolver(HttpClientOptions co) { + LoadBalancer _loadBalancer = loadBalancer; + AddressResolver _addressResolver = addressResolver; + if (_loadBalancer != null) { + if (_addressResolver == null) { + _addressResolver = vertx.hostnameResolver(); + } + } else { + if (_addressResolver != null) { + _loadBalancer = LoadBalancer.ROUND_ROBIN; + } + } + EndpointResolver resolver = endpointResolver; + if (endpointResolver == null && _addressResolver != null) { + resolver = new EndpointResolverImpl<>(_addressResolver.resolver(vertx), _loadBalancer, co.getKeepAliveTimeout() * 1000); + } + return resolver; + } + + @Override + public HttpClient build() { + HttpClientOptions co = clientOptions != null ? clientOptions : new HttpClientOptions(); + PoolOptions po = poolOptions != null ? poolOptions : new PoolOptions(); + CloseFuture cf = resolveCloseFuture(); + HttpClient client; + Closeable closeable; + EndpointResolver resolver = endpointResolver(co); + if (co.isShared()) { + CloseFuture closeFuture = new CloseFuture(); + client = vertx.createSharedResource("__vertx.shared.httpClients", co.getName(), closeFuture, cf_ -> { + + HttpClientImpl impl = new HttpClientImpl(vertx, resolver, co, po); + cf_.add(completion -> impl.close().onComplete(completion)); + return impl; + }); + client = new CleanableHttpClient((HttpClientInternal) client, vertx.cleaner(), (timeout, timeunit) -> closeFuture.close()); + closeable = closeFuture; + } else { + HttpClientImpl impl = new HttpClientImpl(vertx, resolver, co, po); + closeable = impl; + client = new CleanableHttpClient(impl, vertx.cleaner(), impl::close); + } + cf.add(closeable); + if (redirectHandler != null) { + ((HttpClientImpl)((CleanableHttpClient)client).delegate).redirectHandler(redirectHandler); + } + if (connectHandler != null) { + ((HttpClientImpl)((CleanableHttpClient)client).delegate).connectionHandler(connectHandler); + } + return client; + } +} diff --git a/src/main/java/io/vertx/core/http/impl/HttpClientConnection.java b/src/main/java/io/vertx/core/http/impl/HttpClientConnection.java index dad8aec13f0..6614daac91b 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpClientConnection.java +++ b/src/main/java/io/vertx/core/http/impl/HttpClientConnection.java @@ -13,13 +13,14 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; -import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.core.Handler; +import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpConnection; import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; +import io.vertx.core.net.HostAndPort; /** * @author Julien Viet @@ -34,6 +35,8 @@ public interface HttpClientConnection extends HttpConnection { Handler DEFAULT_CONCURRENCY_CHANGE_HANDLER = concurrency -> {}; + HostAndPort authority(); + /** * Set a {@code handler} called when the connection should be evicted from a pool. * @@ -56,6 +59,11 @@ public interface HttpClientConnection extends HttpConnection { */ long concurrency(); + /** + * @return the number of active streams + */ + long activeStreams(); + /** * @return the connection channel */ @@ -66,6 +74,14 @@ public interface HttpClientConnection extends HttpConnection { */ ChannelHandlerContext channelHandlerContext(); + /** + * Create an HTTP stream. + * + * @param context the stream context + * @return a future notified with the created stream + */ + Future createRequest(ContextInternal context); + /** * Create an HTTP stream. * diff --git a/src/main/java/io/vertx/core/http/impl/HttpClientImpl.java b/src/main/java/io/vertx/core/http/impl/HttpClientImpl.java index b4b66cc0ff3..6a581e72c1a 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpClientImpl.java +++ b/src/main/java/io/vertx/core/http/impl/HttpClientImpl.java @@ -15,40 +15,26 @@ import io.vertx.core.Handler; import io.vertx.core.MultiMap; import io.vertx.core.Promise; -import io.vertx.core.http.HttpClient; -import io.vertx.core.http.HttpClientOptions; -import io.vertx.core.http.HttpClientRequest; -import io.vertx.core.http.HttpClientResponse; -import io.vertx.core.http.HttpConnection; -import io.vertx.core.http.HttpHeaders; -import io.vertx.core.http.HttpMethod; -import io.vertx.core.http.HttpVersion; -import io.vertx.core.http.RequestOptions; -import io.vertx.core.http.WebSocket; -import io.vertx.core.http.WebSocketConnectOptions; -import io.vertx.core.http.WebsocketVersion; +import io.vertx.core.http.*; import io.vertx.core.impl.*; +import io.vertx.core.loadbalancing.LoadBalancer; import io.vertx.core.net.*; -import io.vertx.core.net.impl.NetClientBuilder; -import io.vertx.core.net.impl.NetClientInternal; -import io.vertx.core.net.impl.ProxyFilter; +import io.vertx.core.net.impl.endpoint.EndpointManager; +import io.vertx.core.net.impl.endpoint.EndpointProvider; import io.vertx.core.net.impl.pool.*; +import io.vertx.core.spi.resolver.endpoint.EndpointRequest; +import io.vertx.core.net.impl.resolver.EndpointResolverImpl; +import io.vertx.core.spi.resolver.endpoint.EndpointLookup; import io.vertx.core.spi.metrics.ClientMetrics; -import io.vertx.core.spi.metrics.HttpClientMetrics; -import io.vertx.core.spi.metrics.Metrics; import io.vertx.core.spi.metrics.MetricsProvider; -import io.vertx.core.spi.resolver.AddressResolver; +import io.vertx.core.spi.resolver.endpoint.EndpointResolver; import java.lang.ref.WeakReference; import java.net.URI; -import java.net.URISyntaxException; import java.util.*; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; -import java.util.function.Predicate; import java.util.regex.Pattern; -import java.util.stream.Collectors; import static io.vertx.core.http.HttpHeaders.*; @@ -57,7 +43,7 @@ * * @author Tim Fox */ -public class HttpClientImpl implements HttpClientInternal, MetricsProvider { +public class HttpClientImpl extends HttpClientBase implements HttpClientInternal, MetricsProvider { // Pattern to check we are not dealing with an absoluate URI private static final Pattern ABS_URI_START_PATTERN = Pattern.compile("^\\p{Alpha}[\\p{Alpha}\\p{Digit}+.\\-]*:"); @@ -115,68 +101,31 @@ public class HttpClientImpl implements HttpClientInternal, MetricsProvider { } }; - private final VertxInternal vertx; - private final HttpClientOptions options; - private final EndpointProvider> httpEndpointProvider; - private final ConnectionManager webSocketCM; - private final ConnectionManager> httpCM; - private final NetClientInternal netClient; - private final HttpClientMetrics metrics; - private final boolean keepAlive; - private final boolean pipelining; - private final CloseSequence closeSequence; - private EndpointResolver, ?> endpointResolver; - private long closeTimeout = 0L; - private TimeUnit closeTimeoutUnit = TimeUnit.SECONDS; + private final PoolOptions poolOptions; + private final EndpointManager httpCM; + private final EndpointResolver endpointResolver; + private volatile Function> redirectHandler = DEFAULT_HANDLER; private long timerID; - private Predicate proxyFilter; private volatile Handler connectionHandler; - private volatile Function> redirectHandler = DEFAULT_HANDLER; - private final Function contextProvider; - - public HttpClientImpl(VertxInternal vertx, HttpClientOptions options) { - this.vertx = vertx; - this.metrics = vertx.metricsSPI() != null ? vertx.metricsSPI().createHttpClientMetrics(options) : null; - this.options = new HttpClientOptions(options); - this.closeSequence = new CloseSequence(this::doClose, this::doShutdown); - List alpnVersions = options.getAlpnVersions(); - if (alpnVersions == null || alpnVersions.isEmpty()) { - switch (options.getProtocolVersion()) { - case HTTP_2: - alpnVersions = Arrays.asList(HttpVersion.HTTP_2, HttpVersion.HTTP_1_1); - break; - default: - alpnVersions = Collections.singletonList(options.getProtocolVersion()); - break; - } - } - this.keepAlive = options.isKeepAlive(); - this.pipelining = options.isPipelining(); - if (!keepAlive && pipelining) { - throw new IllegalStateException("Cannot have pipelining with no keep alive"); - } - this.proxyFilter = options.getNonProxyHosts() != null ? ProxyFilter.nonProxyHosts(options.getNonProxyHosts()) : ProxyFilter.DEFAULT_PROXY_FILTER; - this.netClient = new NetClientBuilder(vertx, new NetClientOptions(options) - .setHostnameVerificationAlgorithm(options.isVerifyHost() ? "HTTPS": "") - .setProxyOptions(null) - .setApplicationLayerProtocols(alpnVersions - .stream() - .map(HttpVersion::alpnName) - .collect(Collectors.toList()))) - .metrics(metrics) - .build(); - httpEndpointProvider = httpEndpointProvider(); - webSocketCM = webSocketConnectionManager(); - httpCM = new ConnectionManager<>(httpEndpointProvider); - endpointResolver = null; - if (options.getPoolCleanerPeriod() > 0 && (options.getKeepAliveTimeout() > 0L || options.getHttp2KeepAliveTimeout() > 0L)) { + private final Function contextProvider; + + public HttpClientImpl(VertxInternal vertx, + EndpointResolver endpointResolver, + HttpClientOptions options, + PoolOptions poolOptions) { + super(vertx, options); + + this.endpointResolver = endpointResolver; + this.poolOptions = poolOptions; + httpCM = new EndpointManager<>(); + if (poolOptions.getCleanerPeriod() > 0 && (options.getKeepAliveTimeout() > 0L || options.getHttp2KeepAliveTimeout() > 0L)) { PoolChecker checker = new PoolChecker(this); ContextInternal timerContext = vertx.createEventLoopContext(); - timerID = timerContext.setTimer(options.getPoolCleanerPeriod(), checker); + timerID = timerContext.setTimer(poolOptions.getCleanerPeriod(), checker); } - int eventLoopSize = options.getPoolEventLoopSize(); + int eventLoopSize = poolOptions.getEventLoopSize(); if (eventLoopSize > 0) { - EventLoopContext[] eventLoops = new EventLoopContext[eventLoopSize]; + ContextInternal[] eventLoops = new ContextInternal[eventLoopSize]; for (int i = 0;i < eventLoopSize;i++) { eventLoops[i] = vertx.createEventLoopContext(); } @@ -190,18 +139,8 @@ public HttpClientImpl(VertxInternal vertx, HttpClientOptions options) { } } - public NetClientInternal netClient() { - return netClient; - } - - @Override - public Future closeFuture() { - return closeSequence.future(); - } - - @Override - public void close(Promise completion) { - closeSequence.close(completion); + Function contextProvider() { + return contextProvider; } /** @@ -224,335 +163,109 @@ public void handle(Long event) { } } - private void checkExpired(Handler checker) { - httpCM.checkExpired(); - if (endpointResolver != null) { - endpointResolver.checkExpired(); - } + protected void checkExpired(Handler checker) { synchronized (this) { if (!closeSequence.started()) { - timerID = vertx.setTimer(options.getPoolCleanerPeriod(), checker); + timerID = vertx.setTimer(poolOptions.getCleanerPeriod(), checker); } } + httpCM.checkExpired(); + if (endpointResolver != null) { + endpointResolver.checkExpired(); + } } - private EndpointProvider> httpEndpointProvider() { + private EndpointProvider httpEndpointProvider() { return (key, dispose) -> { - int maxPoolSize = Math.max(options.getMaxPoolSize(), options.getHttp2MaxPoolSize()); - ClientMetrics metrics = HttpClientImpl.this.metrics != null ? HttpClientImpl.this.metrics.createEndpointMetrics(key.serverAddr, maxPoolSize) : null; + int maxPoolSize = Math.max(poolOptions.getHttp1MaxSize(), poolOptions.getHttp2MaxSize()); + ClientMetrics metrics = HttpClientImpl.this.metrics != null ? HttpClientImpl.this.metrics.createEndpointMetrics(key.server, maxPoolSize) : null; ProxyOptions proxyOptions = key.proxyOptions; if (proxyOptions != null && !key.ssl && proxyOptions.getType() == ProxyType.HTTP) { SocketAddress server = SocketAddress.inetSocketAddress(proxyOptions.getPort(), proxyOptions.getHost()); - key = new EndpointKey(key.ssl, proxyOptions, server, key.peerAddr); + key = new EndpointKey(key.ssl, key.sslOptions, proxyOptions, server, key.authority); proxyOptions = null; } - HttpChannelConnector connector = new HttpChannelConnector(HttpClientImpl.this, netClient, proxyOptions, metrics, options.getProtocolVersion(), key.ssl, options.isUseAlpn(), key.peerAddr, key.serverAddr); + HttpChannelConnector connector = new HttpChannelConnector(HttpClientImpl.this, netClient, key.sslOptions, proxyOptions, metrics, options.getProtocolVersion(), key.ssl, options.isUseAlpn(), key.authority, key.server); return new SharedClientHttpStreamEndpoint( HttpClientImpl.this, metrics, - options.getMaxWaitQueueSize(), - options.getMaxPoolSize(), - options.getHttp2MaxPoolSize(), + poolOptions.getMaxWaitQueueSize(), + poolOptions.getHttp1MaxSize(), + poolOptions.getHttp2MaxSize(), connector, dispose); }; } - private ConnectionManager webSocketConnectionManager() { - EndpointProvider provider = (key, dispose) -> { - int maxPoolSize = options.getMaxWebSockets(); - ClientMetrics metrics = HttpClientImpl.this.metrics != null ? HttpClientImpl.this.metrics.createEndpointMetrics(key.serverAddr, maxPoolSize) : null; - HttpChannelConnector connector = new HttpChannelConnector(HttpClientImpl.this, netClient, key.proxyOptions, metrics, HttpVersion.HTTP_1_1, key.ssl, false, key.peerAddr, key.serverAddr); - return new WebSocketEndpoint(null, maxPoolSize, connector, dispose); - }; - return new ConnectionManager<>(provider); - } - - Function contextProvider() { - return contextProvider; - } - - private int getPort(RequestOptions request) { - Integer port = request.getPort(); - if (port != null) { - return port; - } - SocketAddress server = request.getServer(); - if (server != null && server.isInetSocket()) { - return server.port(); - } - return options.getDefaultPort(); - } - - private ProxyOptions getProxyOptions(ProxyOptions proxyOptions) { - if (proxyOptions == null) { - proxyOptions = options.getProxyOptions(); - } - return proxyOptions; - } - - private String getHost(RequestOptions request) { - String host = request.getHost(); - if (host != null) { - return host; - } - SocketAddress server = request.getServer(); - if (server != null && server.isInetSocket()) { - return server.host(); - } - return options.getDefaultHost(); - } - - private ProxyOptions computeProxyOptions(ProxyOptions proxyOptions, SocketAddress addr) { - proxyOptions = getProxyOptions(proxyOptions); - if (proxyFilter != null) { - if (!proxyFilter.test(addr)) { - proxyOptions = null; + @Override + protected void doShutdown(Promise p) { + synchronized (this) { + if (timerID >= 0) { + vertx.cancelTimer(timerID); + timerID = -1; } } - return proxyOptions; - } - - HttpClientMetrics metrics() { - return metrics; - } - - /** - * Connect to a server. - */ - public Future connect(SocketAddress server) { - return connect(server, null); - } - - /** - * Connect to a server. - */ - public Future connect(SocketAddress server, SocketAddress peer) { - EventLoopContext context = (EventLoopContext) vertx.getOrCreateContext(); - HttpChannelConnector connector = new HttpChannelConnector(this, netClient, null, null, options.getProtocolVersion(), options.isSsl(), options.isUseAlpn(), peer, server); - return connector.httpConnect(context); - } - - private Future webSocket(ContextInternal ctx, WebSocketConnectOptions connectOptions) { - int port = getPort(connectOptions); - String host = getHost(connectOptions); - SocketAddress addr = SocketAddress.inetSocketAddress(port, host); - ProxyOptions proxyOptions = computeProxyOptions(connectOptions.getProxyOptions(), addr); - EndpointKey key = new EndpointKey(connectOptions.isSsl() != null ? connectOptions.isSsl() : options.isSsl(), proxyOptions, addr, addr); - EventLoopContext eventLoopContext; - if (ctx instanceof EventLoopContext) { - eventLoopContext = (EventLoopContext) ctx; - } else { - eventLoopContext = vertx.createEventLoopContext(ctx.nettyEventLoop(), ctx.workerPool(), ctx.classLoader()); - } - // Work around for workers - return ctx - .succeededFuture() - .compose(v -> webSocketCM.getConnection(eventLoopContext, key)) - .compose(c -> { - Http1xClientConnection conn = (Http1xClientConnection) c; - return conn.toWebSocket( - ctx, - connectOptions.getURI(), - connectOptions.getHeaders(), - connectOptions.getAllowOriginHeader(), - connectOptions.getVersion(), - connectOptions.getSubProtocols(), - connectOptions.getTimeout(), - connectOptions.isRegisterWriteHandlers(), - HttpClientImpl.this.options.getMaxWebSocketFrameSize()); - }); + httpCM.shutdown(); + super.doShutdown(p); } @Override - public Future webSocket(int port, String host, String requestURI) { - return webSocket(new WebSocketConnectOptions().setURI(requestURI).setHost(host).setPort(port)); + protected void doClose(Promise p) { + httpCM.close(); + super.doClose(p); } - @Override - public Future webSocket(String host, String requestURI) { - return webSocket(options.getDefaultPort(), host, requestURI); + public void redirectHandler(Function> handler) { + if (handler == null) { + handler = DEFAULT_HANDLER; + } + redirectHandler = handler; } - @Override - public Future webSocket(String requestURI) { - return webSocket(options.getDefaultPort(), options.getDefaultHost(), requestURI); + public Function> redirectHandler() { + return redirectHandler; } - @Override - public Future webSocket(WebSocketConnectOptions options) { - return webSocket(vertx.getOrCreateContext(), options); + public void connectionHandler(Handler handler) { + connectionHandler = handler; } - @Override - public Future webSocketAbs(String url, MultiMap headers, WebsocketVersion version, List subProtocols) { - URI uri; - try { - uri = new URI(url); - } catch (URISyntaxException e) { - throw new IllegalArgumentException(e); - } - String scheme = uri.getScheme(); - if (!"ws".equals(scheme) && !"wss".equals(scheme)) { - throw new IllegalArgumentException("Scheme: " + scheme); - } - boolean ssl = scheme.length() == 3; - int port = uri.getPort(); - if (port == -1) port = ssl ? 443 : 80; - StringBuilder relativeUri = new StringBuilder(); - if (uri.getRawPath() != null) { - relativeUri.append(uri.getRawPath()); - } - if (uri.getRawQuery() != null) { - relativeUri.append('?').append(uri.getRawQuery()); - } - if (uri.getRawFragment() != null) { - relativeUri.append('#').append(uri.getRawFragment()); - } - WebSocketConnectOptions options = new WebSocketConnectOptions() - .setHost(uri.getHost()) - .setPort(port).setSsl(ssl) - .setURI(relativeUri.toString()) - .setHeaders(headers) - .setVersion(version) - .setSubProtocols(subProtocols); - return webSocket(options); + Handler connectionHandler() { + return connectionHandler; } @Override public Future request(RequestOptions request) { - SocketAddress server = request.getServer(); + Address addr = request.getServer(); Integer port = request.getPort(); String host = request.getHost(); - if (server == null) { + if (addr == null) { if (port == null) { port = options.getDefaultPort(); } if (host == null) { host = options.getDefaultHost(); } - server = SocketAddress.inetSocketAddress(port, host); - } else { + addr = SocketAddress.inetSocketAddress(port, host); + } else if (addr instanceof SocketAddress) { + SocketAddress socketAddr = (SocketAddress) addr; if (port == null) { - port = server.port(); + port = request.getPort(); } if (host == null) { - host = server.host(); + host = request.getHost(); } - } - - return doRequest(server, port, host, request); - } - - @Override - public Future request(HttpMethod method, int port, String host, String requestURI) { - return request(new RequestOptions().setMethod(method).setPort(port).setHost(host).setURI(requestURI)); - } - - @Override - public Future request(Address address, HttpMethod method, int port, String host, String requestURI) { - return doRequest(address, port, host, new RequestOptions().setMethod(method).setPort(port).setHost(host).setURI(requestURI)); - } - - @Override - public Future request(HttpMethod method, String host, String requestURI) { - return request(method, options.getDefaultPort(), host, requestURI); - } - - @Override - public Future request(HttpMethod method, String requestURI) { - return request(method, options.getDefaultPort(), options.getDefaultHost(), requestURI); - } - - private void doShutdown(Promise p) { - synchronized (this) { - if (timerID >= 0) { - vertx.cancelTimer(timerID); - timerID = -1; + if (port == null) { + port = socketAddr.port(); + } + if (host == null) { + host = socketAddr.host(); } } - httpCM.shutdown(); - webSocketCM.shutdown(); - netClient.shutdown(closeTimeout, closeTimeoutUnit).onComplete(p); - } - - private void doClose(Promise p) { - httpCM.close(); - webSocketCM.close(); - netClient.close().onComplete(p); - } - - @Override - public Future close(long timeout, TimeUnit timeUnit) { - this.closeTimeout = timeout; - this.closeTimeoutUnit = timeUnit; - return closeSequence.close(); - } - - @Override - public boolean isMetricsEnabled() { - return getMetrics() != null; - } - - @Override - public Metrics getMetrics() { - return metrics; - } - - @Override - public Future updateSSLOptions(SSLOptions options) { - return netClient.updateSSLOptions(options); - } - - @Override - public HttpClient connectionHandler(Handler handler) { - connectionHandler = handler; - return this; - } - - Handler connectionHandler() { - return connectionHandler; - } - - @Override - public HttpClient redirectHandler(Function> handler) { - if (handler == null) { - handler = DEFAULT_HANDLER; - } - redirectHandler = handler; - return this; - } - - @Override - public Function> redirectHandler() { - return redirectHandler; - } - - public HttpClient proxyFilter(Predicate filter) { - proxyFilter = filter; - return this; - } - - @Override - public HttpClientOptions options() { - return options; - } - - @Override - public VertxInternal vertx() { - return vertx; - } - - public void addressResolver(AddressResolver addressResolver) { - if (addressResolver != null) { - this.endpointResolver = new EndpointResolver<>(httpEndpointProvider, addressResolver, - (key, addr) -> new EndpointKey(key.ssl, key.proxyOptions, addr, addr)); - } else { - this.endpointResolver = null; - } + return doRequest(addr, port, host, request); } - private Future doRequest(Address server, int port, String host, RequestOptions request) { + private Future doRequest(Address server, Integer port, String host, RequestOptions request) { if (server == null) { throw new NullPointerException(); } @@ -560,10 +273,20 @@ private Future doRequest(Address server, int port, String hos String requestURI = request.getURI(); Boolean ssl = request.isSsl(); MultiMap headers = request.getHeaders(); - long timeout = request.getTimeout(); + long connectTimeout = 0L; + long idleTimeout = 0L; + if (request.getTimeout() >= 0L) { + connectTimeout = request.getTimeout(); + idleTimeout = request.getTimeout(); + } + if (request.getConnectTimeout() >= 0L) { + connectTimeout = request.getConnectTimeout(); + } + if (request.getIdleTimeout() >= 0L) { + idleTimeout = request.getIdleTimeout(); + } Boolean followRedirects = request.getFollowRedirects(); Objects.requireNonNull(method, "no null method accepted"); - Objects.requireNonNull(host, "no null host accepted"); Objects.requireNonNull(requestURI, "no null requestURI accepted"); boolean useAlpn = this.options.isUseAlpn(); boolean useSSL = ssl != null ? ssl : this.options.isSsl(); @@ -571,130 +294,164 @@ private Future doRequest(Address server, int port, String hos return vertx.getOrCreateContext().failedFuture("Must enable ALPN when using H2"); } checkClosed(); - String peerHost = host; - if (peerHost.endsWith(".")) { - peerHost = peerHost.substring(0, peerHost.length() - 1); + HostAndPort authority; + // should we do that here ? it might create issues with address resolver that resolves this later + if (host != null && port != null) { + String peerHost = host; + if (peerHost.endsWith(".")) { + peerHost = peerHost.substring(0, peerHost.length() - 1); + } + authority = HostAndPort.create(peerHost, port); + } else { + authority = null; } - SocketAddress peerAddress = SocketAddress.inetSocketAddress(port, peerHost); - return doRequest(method, peerAddress, server, host, port, useSSL, requestURI, headers, request.getTraceOperation(), timeout, followRedirects, request.getProxyOptions()); + ClientSSLOptions sslOptions = sslOptions(request); + return doRequest(method, authority, server, useSSL, requestURI, headers, request.getTraceOperation(), connectTimeout, idleTimeout, followRedirects, sslOptions, request.getProxyOptions()); } private Future doRequest( HttpMethod method, - SocketAddress peerAddress, + HostAndPort authority, Address server, - String host, - int port, Boolean useSSL, String requestURI, MultiMap headers, String traceOperation, - long timeout, + long connectTimeout, + long idleTimeout, Boolean followRedirects, + ClientSSLOptions sslOptions, ProxyOptions proxyConfig) { ContextInternal ctx = vertx.getOrCreateContext(); ContextInternal connCtx = ctx.isEventLoopContext() ? ctx : vertx.createEventLoopContext(ctx.nettyEventLoop(), ctx.workerPool(), ctx.classLoader()); Promise promise = ctx.promise(); - Future future; - ProxyOptions proxyOptions; - if (server instanceof SocketAddress) { - proxyOptions = computeProxyOptions(proxyConfig, (SocketAddress) server); - EndpointKey key = new EndpointKey(useSSL, proxyOptions, (SocketAddress) server, peerAddress); - future = httpCM.withEndpoint(key, endpoint -> { - Future> fut = endpoint.getConnection(connCtx, timeout); + Future future; + if (endpointResolver != null && endpointResolver.accepts(server) != null) { + Future fut = endpointResolver.lookupEndpoint(ctx, server); + future = fut.compose(lookup -> { + SocketAddress address = lookup.address(); + ProxyOptions proxyOptions = computeProxyOptions(proxyConfig, address); + EndpointKey key = new EndpointKey(useSSL, sslOptions, proxyOptions, address, authority != null ? authority : HostAndPort.create(address.host(), address.port())); + return httpCM.withEndpointAsync(key, httpEndpointProvider(), (endpoint, created) -> { + Future> fut2 = endpoint.requestConnection(connCtx, connectTimeout); + if (fut2 == null) { + return null; + } else { + EndpointRequest endpointRequest = lookup.initiateRequest(); + return fut2.andThen(ar -> { + if (ar.failed()) { + endpointRequest.reportFailure(ar.cause()); + } + }).compose(lease -> { + HttpClientConnection conn = lease.get(); + return conn.createStream(ctx).map(stream -> { + HttpClientStream wrapped = new StatisticsGatheringHttpClientStream(stream, endpointRequest); + wrapped.closeHandler(v -> lease.recycle()); + return new ConnectionObtainedResult(proxyOptions, wrapped); + }); + }); + } + }); + }); + } else if (server instanceof SocketAddress) { + ProxyOptions proxyOptions = computeProxyOptions(proxyConfig, (SocketAddress) server); + EndpointKey key = new EndpointKey(useSSL, sslOptions, proxyOptions, (SocketAddress) server, authority); + future = httpCM.withEndpointAsync(key, httpEndpointProvider(), (endpoint, created) -> { + Future> fut = endpoint.requestConnection(connCtx, connectTimeout); if (fut == null) { - return Optional.empty(); + return null; } else { - return Optional.of(fut.compose(lease -> { + return fut.compose(lease -> { HttpClientConnection conn = lease.get(); - return conn.createStream(ctx).andThen(ar -> { - if (ar.succeeded()) { - HttpClientStream stream = ar.result(); - stream.closeHandler(v -> { - lease.recycle(); - }); - } + return conn.createStream(ctx).map(stream -> { + stream.closeHandler(v -> { + lease.recycle(); + }); + return new ConnectionObtainedResult(proxyOptions, stream); }); - })); + }); } }); } else { - EndpointKey key = new EndpointKey(useSSL, proxyConfig, SocketAddress.inetSocketAddress(port, host), peerAddress); - future = endpointResolver.withEndpoint(server, key, endpoint -> createDecoratedHttpClientStream( - timeout, - ctx, - connCtx, - (EndpointResolver.ResolvedEndpoint>) endpoint)); - if (future != null) { - proxyOptions = proxyConfig; - } else { - proxyOptions = null; - } + return ctx.failedFuture("Cannot resolve address " + server); } if (future == null) { return connCtx.failedFuture("Cannot resolve address " + server); } else { - future.map(stream -> { - String u = requestURI; - if (proxyOptions != null && !useSSL && proxyOptions.getType() == ProxyType.HTTP) { - if (!ABS_URI_START_PATTERN.matcher(u).find()) { - int defaultPort = 80; - String addPort = (port != -1 && port != defaultPort) ? (":" + port) : ""; - u = (useSSL == Boolean.TRUE ? "https://" : "http://") + host + addPort + requestURI; - } - } - HttpClientRequest req = new HttpClientRequestImpl(this, stream, ctx.promise(), useSSL, method, peerAddress, host, port, u, traceOperation); - if (headers != null) { - req.headers().setAll(headers); - } - if (proxyOptions != null && !useSSL && proxyOptions.getType() == ProxyType.HTTP) { - if (proxyOptions.getUsername() != null && proxyOptions.getPassword() != null) { - req.headers().add("Proxy-Authorization", "Basic " + Base64.getEncoder() - .encodeToString((proxyOptions.getUsername() + ":" + proxyOptions.getPassword()).getBytes())); - } - } - if (followRedirects != null) { - req.setFollowRedirects(followRedirects); - } - if (timeout > 0L) { - // Maybe later ? - req.setTimeout(timeout); - } - return req; + future.map(res -> { + return createRequest(res.stream, method, headers, requestURI, res.proxyOptions, useSSL, idleTimeout, followRedirects, traceOperation); }).onComplete(promise); return promise.future(); } } - /** - * Create a decorated {@link HttpClientStream} that will gather stream statistics reported to the {@link AddressResolver} - */ - private static Optional> createDecoratedHttpClientStream( - long timeout, - ContextInternal ctx, - ContextInternal connCtx, - EndpointResolver.ResolvedEndpoint> resolvedEndpoint) { - Future> f = resolvedEndpoint.getConnection(connCtx, timeout); - if (f == null) { - return Optional.empty(); - } else { - return Optional.of(f.compose(lease -> { - HttpClientConnection conn = lease.get(); - return conn - .createStream(ctx) - .map(stream -> { - AddressResolver resolver = resolvedEndpoint.resolver(); - HttpClientStream wrapped = new StatisticsGatheringHttpClientStream<>(stream, resolver, resolvedEndpoint.state(), conn.remoteAddress()); - wrapped.closeHandler(v -> lease.recycle()); - return wrapped; - }); - })); + private static class ConnectionObtainedResult { + private final ProxyOptions proxyOptions; + private final HttpClientStream stream; + public ConnectionObtainedResult(ProxyOptions proxyOptions, HttpClientStream stream) { + this.proxyOptions = proxyOptions; + this.stream = stream; + } + } + + Future createRequest(HttpClientConnection conn, ContextInternal context) { + return conn.createStream(context).map(this::createRequest); + } + + private HttpClientRequest createRequest(HttpClientStream stream) { + HttpClientRequest request = new HttpClientRequestImpl(stream, stream.getContext().promise(), HttpMethod.GET, "/"); + Function> rHandler = redirectHandler; + if (rHandler != null) { + request.setMaxRedirects(options.getMaxRedirects()); + request.redirectHandler(resp -> { + Future fut_ = rHandler.apply(resp); + if (fut_ != null) { + return fut_.compose(this::request); + } else { + return null; + } + }); } + return request; } - private void checkClosed() { - if (closeSequence.started()) { - throw new IllegalStateException("Client is closed"); + private HttpClientRequest createRequest( + HttpClientStream stream, + HttpMethod method, + MultiMap headers, + String requestURI, + ProxyOptions proxyOptions, + Boolean useSSL, + long idleTimeout, + Boolean followRedirects, + String traceOperation) { + String u = requestURI; + HostAndPort authority = stream.connection().authority(); + if (proxyOptions != null && !useSSL && proxyOptions.getType() == ProxyType.HTTP) { + if (!ABS_URI_START_PATTERN.matcher(u).find()) { + int defaultPort = 80; + String addPort = (authority.port() != -1 && authority.port() != defaultPort) ? (":" + authority.port()) : ""; + u = (useSSL == Boolean.TRUE ? "https://" : "http://") + authority.host() + addPort + requestURI; + } + } + HttpClientRequest request = createRequest(stream); + request.setURI(u); + request.setMethod(method); + request.traceOperation(traceOperation); + request.setFollowRedirects(followRedirects == Boolean.TRUE); + if (headers != null) { + request.headers().setAll(headers); + } + if (proxyOptions != null && !useSSL && proxyOptions.getType() == ProxyType.HTTP) { + if (proxyOptions.getUsername() != null && proxyOptions.getPassword() != null) { + request.headers().add("Proxy-Authorization", "Basic " + Base64.getEncoder() + .encodeToString((proxyOptions.getUsername() + ":" + proxyOptions.getPassword()).getBytes())); + } + } + if (idleTimeout > 0L) { + // Maybe later ? + request.idleTimeout(idleTimeout); } + return request; } } diff --git a/src/main/java/io/vertx/core/http/impl/HttpClientInternal.java b/src/main/java/io/vertx/core/http/impl/HttpClientInternal.java index 8671a8a7005..c02fa541e8d 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpClientInternal.java +++ b/src/main/java/io/vertx/core/http/impl/HttpClientInternal.java @@ -13,15 +13,13 @@ import io.vertx.core.Closeable; import io.vertx.core.Future; -import io.vertx.core.http.HttpClient; -import io.vertx.core.http.HttpClientOptions; -import io.vertx.core.http.HttpClientRequest; -import io.vertx.core.http.HttpMethod; +import io.vertx.core.http.*; +import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.VertxInternal; -import io.vertx.core.net.Address; +import io.vertx.core.net.HostAndPort; +import io.vertx.core.net.SocketAddress; import io.vertx.core.net.impl.NetClientInternal; import io.vertx.core.spi.metrics.MetricsProvider; -import io.vertx.core.spi.resolver.AddressResolver; public interface HttpClientInternal extends HttpClient, MetricsProvider, Closeable { @@ -37,11 +35,19 @@ public interface HttpClientInternal extends HttpClient, MetricsProvider, Closeab Future closeFuture(); /** - * Configure the client to use an address resolver. + * Connect to a server. * - * @param addressResolver the address resolver + * @param server the server address */ - void addressResolver(AddressResolver addressResolver); + default Future connect(SocketAddress server) { + return connect(server, null); + } - Future request(Address address, HttpMethod method, int port, String host, String requestURI); + /** + * Connect to a server. + * + * @param server the server address + * @param peer the peer + */ + Future connect(SocketAddress server, HostAndPort peer); } diff --git a/src/main/java/io/vertx/core/http/impl/HttpClientPush.java b/src/main/java/io/vertx/core/http/impl/HttpClientPush.java index afc448318d2..0f4535f2c3c 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpClientPush.java +++ b/src/main/java/io/vertx/core/http/impl/HttpClientPush.java @@ -14,6 +14,7 @@ import io.vertx.core.MultiMap; import io.vertx.core.http.HttpMethod; import io.vertx.core.http.impl.headers.Http2HeadersAdaptor; +import io.vertx.core.net.HostAndPort; import io.vertx.core.net.SocketAddress; import io.vertx.core.spi.observability.HttpRequest; @@ -22,10 +23,9 @@ */ public class HttpClientPush implements HttpRequest { - final int port; final String uri; final HttpMethod method; - final String host; + final HostAndPort authority; final HttpClientStream stream; final MultiMap headers; @@ -36,11 +36,9 @@ public HttpClientPush(Http2Headers headers, HttpClientStream stream) { MultiMap headersMap = new Http2HeadersAdaptor(headers); int pos = authority == null ? -1 : authority.indexOf(':'); if (pos == -1) { - this.host = authority; - this.port = 80; + this.authority = HostAndPort.create(authority, 80); } else { - this.host = authority.substring(0, pos); - this.port = Integer.parseInt(authority.substring(pos + 1)); + this.authority = HostAndPort.create(authority.substring(0, pos), Integer.parseInt(authority.substring(pos + 1))); } this.method = HttpMethod.valueOf(rawMethod); this.uri = headers.path().toString(); diff --git a/src/main/java/io/vertx/core/http/impl/HttpClientRequestBase.java b/src/main/java/io/vertx/core/http/impl/HttpClientRequestBase.java index 5cd19681452..95011a300b8 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpClientRequestBase.java +++ b/src/main/java/io/vertx/core/http/impl/HttpClientRequestBase.java @@ -12,17 +12,14 @@ package io.vertx.core.http.impl; import io.netty.handler.codec.http2.Http2Error; -import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.Promise; -import io.vertx.core.http.HttpClientRequest; -import io.vertx.core.http.HttpClientResponse; -import io.vertx.core.http.HttpMethod; -import io.vertx.core.http.StreamResetException; +import io.vertx.core.http.*; import io.vertx.core.impl.ContextInternal; +import io.vertx.core.impl.NoStackTraceTimeoutException; import io.vertx.core.impl.future.PromiseInternal; -import io.vertx.core.net.SocketAddress; +import io.vertx.core.net.HostAndPort; import java.util.Objects; @@ -31,14 +28,11 @@ */ public abstract class HttpClientRequestBase implements HttpClientRequest { - protected final HttpClientImpl client; protected final ContextInternal context; protected final HttpClientStream stream; - protected final SocketAddress server; protected final boolean ssl; private io.vertx.core.http.HttpMethod method; - private String host; - private int port; + private HostAndPort authority; private String uri; private String path; private String query; @@ -47,18 +41,16 @@ public abstract class HttpClientRequestBase implements HttpClientRequest { private long currentTimeoutTimerId = -1; private long currentTimeoutMs; private long lastDataReceived; + private Throwable reset; - HttpClientRequestBase(HttpClientImpl client, HttpClientStream stream, PromiseInternal responsePromise, boolean ssl, HttpMethod method, SocketAddress server, String host, int port, String uri) { - this.client = client; + HttpClientRequestBase(HttpClientStream stream, PromiseInternal responsePromise, HttpMethod method, String uri) { this.stream = stream; this.responsePromise = responsePromise; this.context = responsePromise.context(); this.uri = uri; this.method = method; - this.server = server; - this.host = host; - this.port = port; - this.ssl = ssl; + this.authority = stream.connection().authority(); + this.ssl = stream.connection().isSsl(); // stream.pushHandler(this::handlePush); @@ -73,10 +65,12 @@ public abstract class HttpClientRequestBase implements HttpClientRequest { } protected String authority() { - if ((port == 80 && !ssl) || (port == 443 && ssl)) { - return host; + if (authority == null) { + return null; + } else if ((authority.port() == 80 && !ssl) || (authority.port() == 443 && ssl) || authority.port() < 0) { + return authority.host(); } else { - return host + ':' + port; + return authority.host() + ':' + authority.port(); } } @@ -118,25 +112,10 @@ public synchronized HttpClientRequest setURI(String uri) { return this; } - public String getHost() { - return host; - } - - @Override - public synchronized HttpClientRequest setHost(String host) { - Objects.requireNonNull(uri); - this.host = host; - return this; - } - - @Override - public int getPort() { - return port; - } - @Override - public synchronized HttpClientRequest setPort(int port) { - this.port = port; + public synchronized HttpClientRequest authority(HostAndPort authority) { + Objects.requireNonNull(authority); + this.authority = authority; return this; } @@ -153,13 +132,20 @@ public synchronized HttpClientRequest setMethod(HttpMethod method) { } @Override - public synchronized HttpClientRequest setTimeout(long timeoutMs) { + public synchronized HttpClientRequest idleTimeout(long timeout) { cancelTimeout(); - currentTimeoutMs = timeoutMs; - currentTimeoutTimerId = context.setTimer(timeoutMs, id -> handleTimeout(timeoutMs)); + currentTimeoutMs = timeout; + currentTimeoutTimerId = context.setTimer(timeout, id -> handleTimeout(timeout)); return this; } + protected Throwable mapException(Throwable t) { + if (t instanceof HttpClosedException && reset != null) { + t = reset; + } + return t; + } + void handleException(Throwable t) { fail(t); } @@ -174,7 +160,7 @@ void fail(Throwable t) { } void handlePush(HttpClientPush push) { - HttpClientRequestPushPromise pushReq = new HttpClientRequestPushPromise(push.stream, client, ssl, push.method, push.uri, push.host, push.port, push.headers); + HttpClientRequestPushPromise pushReq = new HttpClientRequestPushPromise(push.stream, push.method, push.uri, push.headers); if (pushHandler != null) { pushHandler.handle(pushReq); } else { @@ -183,7 +169,9 @@ void handlePush(HttpClientPush push) { } void handleResponse(HttpClientResponse resp) { - handleResponse(responsePromise, resp, cancelTimeout()); + if (reset == null) { + handleResponse(responsePromise, resp, cancelTimeout()); + } } abstract void handleResponse(Promise promise, HttpClientResponse resp, long timeoutMs); @@ -191,7 +179,7 @@ void handleResponse(HttpClientResponse resp) { private synchronized long cancelTimeout() { long ret; if ((ret = currentTimeoutTimerId) != -1) { - client.vertx().cancelTimer(currentTimeoutTimerId); + context.owner().cancelTimer(currentTimeoutTimerId); currentTimeoutTimerId = -1; ret = currentTimeoutMs; currentTimeoutMs = 0; @@ -200,6 +188,7 @@ private synchronized long cancelTimeout() { } private void handleTimeout(long timeoutMs) { + NoStackTraceTimeoutException cause; synchronized (this) { currentTimeoutTimerId = -1; currentTimeoutMs = 0; @@ -209,16 +198,17 @@ private void handleTimeout(long timeoutMs) { if (timeSinceLastData < timeoutMs) { // reschedule lastDataReceived = 0; - setTimeout(timeoutMs - timeSinceLastData); + idleTimeout(timeoutMs - timeSinceLastData); return; } } + cause = timeoutEx(timeoutMs, method, authority, uri); } - reset(timeoutEx(timeoutMs, method, server, uri)); + reset(cause); } - static NoStackTraceTimeoutException timeoutEx(long timeoutMs, HttpMethod method, SocketAddress server, String uri) { - return new NoStackTraceTimeoutException("The timeout period of " + timeoutMs + "ms has been exceeded while executing " + method + " " + uri + " for server " + server); + static NoStackTraceTimeoutException timeoutEx(long timeoutMs, HttpMethod method, HostAndPort peer, String uri) { + return new NoStackTraceTimeoutException("The timeout period of " + timeoutMs + "ms has been exceeded while executing " + method + " " + uri + " for server " + peer); } synchronized void dataReceived() { @@ -237,7 +227,16 @@ public boolean reset(long code, Throwable cause) { return reset(new StreamResetException(code, cause)); } - abstract boolean reset(Throwable cause); + private boolean reset(Throwable cause) { + synchronized (this) { + if (reset != null) { + return false; + } + reset = cause; + } + stream.reset(cause); + return true; + } @Override public Future response() { diff --git a/src/main/java/io/vertx/core/http/impl/HttpClientRequestImpl.java b/src/main/java/io/vertx/core/http/impl/HttpClientRequestImpl.java index 0e928fcd07f..42f73a39304 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpClientRequestImpl.java +++ b/src/main/java/io/vertx/core/http/impl/HttpClientRequestImpl.java @@ -15,15 +15,16 @@ import io.vertx.codegen.annotations.Nullable; import io.vertx.core.*; import io.vertx.core.buffer.Buffer; +import io.vertx.core.buffer.impl.BufferInternal; import io.vertx.core.http.*; import io.vertx.core.http.impl.headers.HeadersMultiMap; import io.vertx.core.impl.Arguments; import io.vertx.core.impl.future.PromiseInternal; import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; -import io.vertx.core.net.SocketAddress; import java.util.Objects; +import java.util.function.Function; import static io.vertx.core.http.HttpHeaders.CONTENT_LENGTH; @@ -50,23 +51,25 @@ public class HttpClientRequestImpl extends HttpClientRequestBase implements Http private Handler earlyHintsHandler; private Handler drainHandler; private Handler exceptionHandler; + private Function> redirectHandler; private boolean ended; - private Throwable reset; - private int followRedirects; + private boolean followRedirects; + private int maxRedirects; + private int numberOfRedirections; private HeadersMultiMap headers; private StreamPriority priority; private boolean headWritten; private boolean isConnect; private String traceOperation; - HttpClientRequestImpl(HttpClientImpl client, HttpClientStream stream, PromiseInternal responsePromise, boolean ssl, HttpMethod method, - SocketAddress server, String host, int port, String requestURI, String traceOperation) { - super(client, stream, responsePromise, ssl, method, server, host, port, requestURI); + HttpClientRequestImpl(HttpClientStream stream, PromiseInternal responsePromise, HttpMethod method, + String requestURI) { + super(stream, responsePromise, method, requestURI); this.chunked = false; this.endPromise = context.promise(); this.endFuture = endPromise.future(); this.priority = HttpUtils.DEFAULT_STREAM_PRIORITY; - this.traceOperation = traceOperation; + this.numberOfRedirections = 0; // stream.continueHandler(this::handleContinue); @@ -77,6 +80,7 @@ public class HttpClientRequestImpl extends HttpClientRequestBase implements Http @Override void handleException(Throwable t) { + t = mapException(t); super.handleException(t); if (endPromise.tryFail(t)) { Handler handler = exceptionHandler(); @@ -95,22 +99,33 @@ void handleException(Throwable t) { @Override public synchronized HttpClientRequest setFollowRedirects(boolean followRedirects) { checkEnded(); - if (followRedirects) { - this.followRedirects = client.options().getMaxRedirects() - 1; - } else { - this.followRedirects = 0; - } + this.followRedirects = followRedirects; return this; } + @Override + public synchronized boolean isFollowRedirects() { + return followRedirects; + } + @Override public synchronized HttpClientRequest setMaxRedirects(int maxRedirects) { Arguments.require(maxRedirects >= 0, "Max redirects must be >= 0"); checkEnded(); - followRedirects = maxRedirects; + this.maxRedirects = maxRedirects; return this; } + @Override + public synchronized int getMaxRedirects() { + return maxRedirects; + } + + @Override + public int numberOfRedirections() { + return numberOfRedirections; + } + @Override public synchronized HttpClientRequestImpl setChunked(boolean chunked) { checkEnded(); @@ -118,7 +133,7 @@ public synchronized HttpClientRequestImpl setChunked(boolean chunked) { throw new IllegalStateException("Cannot set chunked after data has been written on request"); } // HTTP 1.0 does not support chunking so we ignore this if HTTP 1.0 - if (client.options().getProtocolVersion() != io.vertx.core.http.HttpVersion.HTTP_1_0) { + if (version() != io.vertx.core.http.HttpVersion.HTTP_1_0) { this.chunked = chunked; } return this; @@ -204,7 +219,7 @@ public synchronized HttpClientRequest continueHandler(Handler handler) { } @Override - public HttpClientRequest earlyHintsHandler(@Nullable Handler handler) { + public synchronized HttpClientRequest earlyHintsHandler(@Nullable Handler handler) { if (handler != null) { checkEnded(); } @@ -212,6 +227,15 @@ public HttpClientRequest earlyHintsHandler(@Nullable Handler handler) return this; } + @Override + public synchronized HttpClientRequest redirectHandler(@Nullable Function> handler) { + if (handler != null) { + checkEnded(); + } + this.redirectHandler = handler; + return this; + } + @Override public Future sendHead() { checkEnded(); @@ -220,9 +244,6 @@ public Future sendHead() { @Override public Future connect() { - if (client.options().isPipelining()) { - return context.failedFuture("Cannot upgrade a pipe-lined request"); - } doWrite(null, false, true); return response(); } @@ -242,15 +263,15 @@ public synchronized HttpClientRequest putHeader(CharSequence name, Iterable writeCustomFrame(int type, int flags, Buffer payload) { synchronized (this) { checkEnded(); } - stream.writeFrame(type, flags, payload.getByteBuf()); - return this; + return stream.writeFrame(type, flags, ((BufferInternal)payload).getByteBuf()); } private void handleDrained(Void v) { @@ -287,11 +307,13 @@ private void handleNextRequest(HttpClientRequest next, Handler { if (ar.succeeded()) { if (timeoutMs > 0) { - next.setTimeout(timeoutMs); + next.idleTimeout(timeoutMs); } next.end(); } else { @@ -321,32 +343,24 @@ private void handleEarlyHints(MultiMap headers) { } void handleResponse(Promise promise, HttpClientResponse resp, long timeoutMs) { - if (reset != null) { - return; - } int statusCode = resp.statusCode(); - if (followRedirects > 0 && statusCode >= 300 && statusCode < 400) { - Future next = client.redirectHandler().apply(resp); - if (next != null) { - resp - .end() - .compose(v -> next, err -> next) - .onComplete(ar1 -> { - if (ar1.succeeded()) { - RequestOptions options = ar1.result(); - Future f = client.request(options); - f.onComplete(ar2 -> { - if (ar2.succeeded()) { - handleNextRequest(ar2.result(), promise, timeoutMs); - } else { - fail(ar2.cause()); - } - }); - } else { - fail(ar1.cause()); - } - }); - return; + if (followRedirects && numberOfRedirections < maxRedirects && statusCode >= 300 && statusCode < 400) { + Function> handler = redirectHandler; + if (handler != null) { + Future next = handler.apply(resp); + if (next != null) { + resp + .end() + .compose(v -> next, err -> next) + .onComplete(ar1 -> { + if (ar1.succeeded()) { + handleNextRequest(ar1.result(), promise, timeoutMs); + } else { + fail(ar1.cause()); + } + }); + return; + } } } promise.complete(resp); @@ -354,18 +368,18 @@ void handleResponse(Promise promise, HttpClientResponse resp @Override public Future end(String chunk) { - return write(Buffer.buffer(chunk).getByteBuf(), true); + return write(BufferInternal.buffer(chunk).getByteBuf(), true); } @Override public Future end(String chunk, String enc) { Objects.requireNonNull(enc, "no null encoding accepted"); - return write(Buffer.buffer(chunk, enc).getByteBuf(), true); + return write(BufferInternal.buffer(chunk, enc).getByteBuf(), true); } @Override public Future end(Buffer chunk) { - return write(chunk.getByteBuf(), true); + return write(((BufferInternal)chunk).getByteBuf(), true); } @Override @@ -375,19 +389,19 @@ public Future end() { @Override public Future write(Buffer chunk) { - ByteBuf buf = chunk.getByteBuf(); + ByteBuf buf = ((BufferInternal)chunk).getByteBuf(); return write(buf, false); } @Override public Future write(String chunk) { - return write(Buffer.buffer(chunk).getByteBuf(), false); + return write(BufferInternal.buffer(chunk).getByteBuf(), false); } @Override public Future write(String chunk, String enc) { Objects.requireNonNull(enc, "no null encoding accepted"); - return write(Buffer.buffer(chunk, enc).getByteBuf(), false); + return write(BufferInternal.buffer(chunk, enc).getByteBuf(), false); } private boolean requiresContentLength() { diff --git a/src/main/java/io/vertx/core/http/impl/HttpClientRequestPushPromise.java b/src/main/java/io/vertx/core/http/impl/HttpClientRequestPushPromise.java index ac8f5e6b700..b0c1502026a 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpClientRequestPushPromise.java +++ b/src/main/java/io/vertx/core/http/impl/HttpClientRequestPushPromise.java @@ -12,14 +12,14 @@ package io.vertx.core.http.impl; import io.vertx.codegen.annotations.Nullable; -import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.MultiMap; import io.vertx.core.Promise; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.*; -import io.vertx.core.net.SocketAddress; + +import java.util.function.Function; /** * @author Julien Viet @@ -31,14 +31,10 @@ class HttpClientRequestPushPromise extends HttpClientRequestBase { public HttpClientRequestPushPromise( HttpClientStream stream, - HttpClientImpl client, - boolean ssl, HttpMethod method, String uri, - String host, - int port, MultiMap headers) { - super(client, stream, stream.connection().getContext().promise(), ssl, method, SocketAddress.inetSocketAddress(port, host), host, port, uri); + super(stream, stream.connection().getContext().promise(), method, uri); this.stream = stream; this.headers = headers; } @@ -63,12 +59,6 @@ public HttpConnection connection() { return stream.connection(); } - @Override - boolean reset(Throwable cause) { - stream.reset(cause); - return true; - } - @Override public boolean isChunked() { return false; @@ -99,11 +89,31 @@ public HttpClientRequest setFollowRedirects(boolean followRedirect) { throw new IllegalStateException(); } + @Override + public boolean isFollowRedirects() { + return false; + } + @Override public HttpClientRequest setMaxRedirects(int maxRedirects) { throw new IllegalStateException(); } + @Override + public int getMaxRedirects() { + return 0; + } + + @Override + public int numberOfRedirections() { + return 0; + } + + @Override + public HttpClientRequest redirectHandler(@Nullable Function> handler) { + throw new IllegalStateException(); + } + @Override public HttpClientRequest setChunked(boolean chunked) { throw new IllegalStateException(); @@ -129,6 +139,16 @@ public HttpClientRequest putHeader(CharSequence name, Iterable val throw new IllegalStateException(); } + @Override + public HttpClientRequest traceOperation(String op) { + throw new IllegalStateException(); + } + + @Override + public String traceOperation() { + throw new IllegalStateException(); + } + @Override public Future write(String chunk) { throw new IllegalStateException(); @@ -190,7 +210,7 @@ public StreamPriority getStreamPriority() { } @Override - public HttpClientRequest writeCustomFrame(int type, int flags, Buffer payload) { + public Future writeCustomFrame(int type, int flags, Buffer payload) { throw new UnsupportedOperationException("Cannot write frame with HTTP/1.x "); } } diff --git a/src/main/java/io/vertx/core/http/impl/HttpClientStream.java b/src/main/java/io/vertx/core/http/impl/HttpClientStream.java index ff91c482743..aa1c825cbd9 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpClientStream.java +++ b/src/main/java/io/vertx/core/http/impl/HttpClientStream.java @@ -17,6 +17,7 @@ import io.vertx.core.Handler; import io.vertx.core.MultiMap; import io.vertx.core.buffer.Buffer; +import io.vertx.core.buffer.impl.BufferInternal; import io.vertx.core.http.HttpFrame; import io.vertx.core.http.HttpVersion; import io.vertx.core.http.StreamPriority; @@ -48,7 +49,7 @@ public interface HttpClientStream extends WriteStream { Future writeHead(HttpRequestHead request, boolean chunked, ByteBuf buf, boolean end, StreamPriority priority, boolean connect); Future writeBuffer(ByteBuf buf, boolean end); - void writeFrame(int type, int flags, ByteBuf payload); + Future writeFrame(int type, int flags, ByteBuf payload); void continueHandler(Handler handler); void earlyHintsHandler(Handler handler); @@ -57,12 +58,12 @@ public interface HttpClientStream extends WriteStream { @Override default Future write(Buffer data) { - return writeBuffer(data.getByteBuf(), false); + return writeBuffer(((BufferInternal)data).getByteBuf(), false); } @Override default Future end(Buffer data) { - return writeBuffer(data.getByteBuf(), true); + return writeBuffer(((BufferInternal)data).getByteBuf(), true); } @Override diff --git a/src/main/java/io/vertx/core/http/impl/HttpNetSocket.java b/src/main/java/io/vertx/core/http/impl/HttpNetSocket.java index 40d7b16459b..c0752f5aa86 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpNetSocket.java +++ b/src/main/java/io/vertx/core/http/impl/HttpNetSocket.java @@ -19,8 +19,10 @@ import io.vertx.core.http.StreamResetException; import io.vertx.core.impl.ContextInternal; import io.vertx.core.net.NetSocket; +import io.vertx.core.net.SSLOptions; import io.vertx.core.net.SocketAddress; import io.vertx.core.net.impl.ConnectionBase; +import io.vertx.core.net.impl.NetSocketInternal; import io.vertx.core.streams.ReadStream; import io.vertx.core.streams.WriteStream; @@ -200,7 +202,7 @@ public Future sendFile(String filename, long offset, long length) { .pipe() .endOnComplete(false) .to(this) - .eventually(v -> file.close()) + .eventually(file::close) ); } @@ -237,6 +239,12 @@ public NetSocket closeHandler(@Nullable Handler handler) { return this; } + @Override + public NetSocket shutdownHandler(@Nullable Handler handler) { + // Not sure, we can do something here + return this; + } + Handler closeHandler() { synchronized (conn) { return closeHandler; @@ -244,12 +252,12 @@ Handler closeHandler() { } @Override - public Future upgradeToSsl() { + public Future upgradeToSsl(String serverName) { return Future.failedFuture("Cannot upgrade stream to SSL"); } @Override - public Future upgradeToSsl(String serverName) { + public Future upgradeToSsl(SSLOptions sslOptions, String serverName) { return Future.failedFuture("Cannot upgrade stream to SSL"); } diff --git a/src/main/java/io/vertx/core/http/impl/HttpServerImpl.java b/src/main/java/io/vertx/core/http/impl/HttpServerImpl.java index f2f4c658ac3..410e348f0a4 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpServerImpl.java +++ b/src/main/java/io/vertx/core/http/impl/HttpServerImpl.java @@ -11,23 +11,22 @@ package io.vertx.core.http.impl; -import io.netty.channel.Channel; import io.netty.handler.traffic.GlobalTrafficShapingHandler; import io.vertx.core.*; import io.vertx.core.http.*; import io.vertx.core.impl.ContextInternal; -import io.vertx.core.impl.EventLoopContext; import io.vertx.core.impl.future.PromiseInternal; import io.vertx.core.impl.VertxInternal; import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; +import io.vertx.core.net.SSLOptions; import io.vertx.core.net.SocketAddress; import io.vertx.core.net.impl.*; import io.vertx.core.spi.metrics.MetricsProvider; import io.vertx.core.spi.metrics.TCPMetrics; import io.vertx.core.spi.metrics.VertxMetrics; -import java.util.function.BiConsumer; +import java.util.List; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -42,15 +41,11 @@ public class HttpServerImpl extends TCPServerBase implements HttpServer, Closeab private static final Handler DEFAULT_EXCEPTION_HANDLER = t -> log.trace("Connection failure", t); - private static final String FLASH_POLICY_HANDLER_PROP_NAME = "vertx.flashPolicyHandler"; private static final String DISABLE_WEBSOCKETS_PROP_NAME = "vertx.disableWebsockets"; - private static final String DISABLE_H2C_PROP_NAME = "vertx.disableH2c"; - static final boolean USE_FLASH_POLICY_HANDLER = Boolean.getBoolean(FLASH_POLICY_HANDLER_PROP_NAME); static final boolean DISABLE_WEBSOCKETS = Boolean.getBoolean(DISABLE_WEBSOCKETS_PROP_NAME); final HttpServerOptions options; - private final boolean disableH2c; private Handler requestHandler; private Handler wsHandler; private Handler invalidRequestHandler; @@ -60,8 +55,17 @@ public class HttpServerImpl extends TCPServerBase implements HttpServer, Closeab public HttpServerImpl(VertxInternal vertx, HttpServerOptions options) { super(vertx, options); - this.options = new HttpServerOptions(options); - this.disableH2c = Boolean.getBoolean(DISABLE_H2C_PROP_NAME) || options.isSsl(); + this.options = (HttpServerOptions) super.options; + } + + @Override + protected void configure(SSLOptions options) { + List applicationProtocols = this.options + .getAlpnVersions() + .stream() + .map(HttpVersion::alpnName) + .collect(Collectors.toList()); + options.setApplicationLayerProtocols(applicationProtocols); } @Override @@ -135,10 +139,10 @@ public Future listen() { } @Override - protected BiConsumer childHandler(ContextInternal context, SocketAddress address, GlobalTrafficShapingHandler trafficShapingHandler) { - EventLoopContext connContext; - if (context instanceof EventLoopContext) { - connContext = (EventLoopContext) context; + protected Worker childHandler(ContextInternal context, SocketAddress address, GlobalTrafficShapingHandler trafficShapingHandler) { + ContextInternal connContext; + if (context.isEventLoopContext()) { + connContext = context; } else { connContext = vertx.createEventLoopContext(context.nettyEventLoop(), context.workerPool(), context.classLoader()); } @@ -154,21 +158,11 @@ protected BiConsumer childHandler(ContextInternal c vertx, options, serverOrigin, - disableH2c, hello, hello.exceptionHandler, trafficShapingHandler); } - @Override - protected SSLHelper createSSLHelper() { - return new SSLHelper(options, options - .getAlpnVersions() - .stream() - .map(HttpVersion::alpnName) - .collect(Collectors.toList())); - } - @Override public synchronized Future listen(SocketAddress address) { if (requestHandler == null && wsHandler == null) { diff --git a/src/main/java/io/vertx/core/http/impl/HttpServerRequestWrapper.java b/src/main/java/io/vertx/core/http/impl/HttpServerRequestWrapper.java index 21399853d08..56c91de58fa 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpServerRequestWrapper.java +++ b/src/main/java/io/vertx/core/http/impl/HttpServerRequestWrapper.java @@ -20,6 +20,7 @@ import io.vertx.core.http.HttpVersion; import io.vertx.core.http.ServerWebSocket; import io.vertx.core.http.StreamPriority; +import io.vertx.core.net.HostAndPort; import io.vertx.core.net.NetSocket; import io.vertx.core.net.SocketAddress; import io.vertx.core.streams.Pipe; @@ -91,12 +92,6 @@ public HttpMethod method() { } @Override - public boolean isSSL() { - return delegate.isSSL(); - } - - @Override - @Nullable public String scheme() { return delegate.scheme(); } @@ -107,21 +102,18 @@ public String uri() { } @Override - @Nullable public String path() { return delegate.path(); } @Override - @Nullable public String query() { return delegate.query(); } @Override - @Nullable - public String host() { - return delegate.host(); + public HostAndPort authority() { + return delegate.authority(); } @Override @@ -130,31 +122,16 @@ public long bytesRead() { } @Override - @CacheReturn public HttpServerResponse response() { return delegate.response(); } @Override - @CacheReturn public MultiMap headers() { return delegate.headers(); } @Override - @Nullable - public String getHeader(String headerName) { - return delegate.getHeader(headerName); - } - - @Override - @GenIgnore(GenIgnore.PERMITTED_TYPE) - public String getHeader(CharSequence headerName) { - return delegate.getHeader(headerName); - } - - @Override - @Fluent public HttpServerRequest setParamsCharset(String charset) { return delegate.setParamsCharset(charset); } @@ -165,40 +142,10 @@ public String getParamsCharset() { } @Override - @CacheReturn public MultiMap params() { return delegate.params(); } - @Override - @Nullable - public String getParam(String paramName) { - return delegate.getParam(paramName); - } - - @Override - public String getParam(String paramName, String defaultValue) { - return delegate.getParam(paramName, defaultValue); - } - - @Override - @CacheReturn - public SocketAddress remoteAddress() { - return delegate.remoteAddress(); - } - - @Override - @CacheReturn - public SocketAddress localAddress() { - return delegate.localAddress(); - } - - @Override - @GenIgnore(GenIgnore.PERMITTED_TYPE) - public SSLSession sslSession() { - return delegate.sslSession(); - } - @Override @GenIgnore public X509Certificate[] peerCertificateChain() throws SSLPeerUnverifiedException { @@ -210,12 +157,6 @@ public String absoluteURI() { return delegate.absoluteURI(); } - @Override - @Fluent - public HttpServerRequest bodyHandler(@Nullable Handler bodyHandler) { - return delegate.bodyHandler(bodyHandler); - } - @Override public Future body() { return delegate.body(); @@ -315,17 +256,6 @@ public DecoderResult decoderResult() { return delegate.getCookie(name, domain, path); } - @Override - public int cookieCount() { - return delegate.cookieCount(); - } - - @Override - @Deprecated - public Map cookieMap() { - return delegate.cookieMap(); - } - @Override public Set cookies(String name) { return delegate.cookies(name); @@ -352,14 +282,4 @@ public Object metric() { return delegate.metric(); } - @Override - public Pipe pipe() { - return delegate.pipe(); - } - - @Override - public Future pipeTo(WriteStream dst) { - return delegate.pipeTo(dst); - } - } diff --git a/src/main/java/io/vertx/core/http/impl/HttpServerWorker.java b/src/main/java/io/vertx/core/http/impl/HttpServerWorker.java index 2f72d3a3616..fab51f31fcb 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpServerWorker.java +++ b/src/main/java/io/vertx/core/http/impl/HttpServerWorker.java @@ -28,16 +28,14 @@ import io.netty.util.concurrent.GenericFutureListener; import io.vertx.core.Handler; import io.vertx.core.http.HttpServerOptions; -import io.vertx.core.http.impl.cgbystrom.FlashPolicyHandler; import io.vertx.core.impl.ContextInternal; -import io.vertx.core.impl.EventLoopContext; import io.vertx.core.impl.VertxInternal; +import io.vertx.core.net.ServerSSLOptions; import io.vertx.core.net.impl.*; import io.vertx.core.spi.metrics.HttpServerMetrics; import java.nio.charset.StandardCharsets; import java.util.List; -import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; @@ -47,9 +45,9 @@ * * @author Julien Viet */ -public class HttpServerWorker implements BiConsumer { +public class HttpServerWorker implements TCPServerBase.Worker { - final EventLoopContext context; + final ContextInternal context; private final Supplier streamContextSupplier; private final VertxInternal vertx; private final HttpServerImpl server; @@ -63,13 +61,12 @@ public class HttpServerWorker implements BiConsumer private final Function encodingDetector; private final GlobalTrafficShapingHandler trafficShapingHandler; - public HttpServerWorker(EventLoopContext context, + public HttpServerWorker(ContextInternal context, Supplier streamContextSupplier, HttpServerImpl server, VertxInternal vertx, HttpServerOptions options, String serverOrigin, - boolean disableH2C, Handler connectionHandler, Handler exceptionHandler, GlobalTrafficShapingHandler trafficShapingHandler) { @@ -92,7 +89,7 @@ public HttpServerWorker(EventLoopContext context, this.options = options; this.serverOrigin = serverOrigin; this.logEnabled = options.getLogActivity(); - this.disableH2C = disableH2C; + this.disableH2C = !options.isHttp2ClearTextEnabled(); this.connectionHandler = connectionHandler; this.exceptionHandler = exceptionHandler; this.compressionOptions = compressionOptions; @@ -101,7 +98,7 @@ public HttpServerWorker(EventLoopContext context, } @Override - public void accept(Channel ch, SslChannelProvider sslChannelProvider) { + public void accept(Channel ch, SslChannelProvider sslChannelProvider, SSLHelper sslHelper, ServerSSLOptions sslOptions) { if (HAProxyMessageCompletionHandler.canUseProxyProtocol(options.isUseProxyProtocol())) { IdleStateHandler idle; io.netty.util.concurrent.Promise p = ch.eventLoop().newPromise(); @@ -117,21 +114,21 @@ public void accept(Channel ch, SslChannelProvider sslChannelProvider) { if (idle != null) { ch.pipeline().remove(idle); } - configurePipeline(future.getNow(), sslChannelProvider); + configurePipeline(future.getNow(), sslChannelProvider, sslHelper); } else { //No need to close the channel.HAProxyMessageDecoder already did handleException(future.cause()); } }); } else { - configurePipeline(ch, sslChannelProvider); + configurePipeline(ch, sslChannelProvider, sslHelper); } } - private void configurePipeline(Channel ch, SslChannelProvider sslChannelProvider) { + private void configurePipeline(Channel ch, SslChannelProvider sslChannelProvider, SSLHelper sslHelper) { ChannelPipeline pipeline = ch.pipeline(); if (options.isSsl()) { - pipeline.addLast("ssl", sslChannelProvider.createServerHandler()); + pipeline.addLast("ssl", sslChannelProvider.createServerHandler(options.isUseAlpn(), options.getSslHandshakeTimeout(), options.getSslHandshakeTimeoutUnit())); ChannelPromise p = ch.newPromise(); pipeline.addLast("handshaker", new SslHandshakeCompletionHandler(p)); p.addListener(future -> { @@ -139,13 +136,25 @@ private void configurePipeline(Channel ch, SslChannelProvider sslChannelProvider if (options.isUseAlpn()) { SslHandler sslHandler = pipeline.get(SslHandler.class); String protocol = sslHandler.applicationProtocol(); - if ("h2".equals(protocol)) { - handleHttp2(ch); + if (protocol != null) { + switch (protocol) { + case "h2": + configureHttp2(ch.pipeline()); + break; + case "http/1.1": + case "http/1.0": + configureHttp1Pipeline(ch.pipeline(), sslChannelProvider, sslHelper); + configureHttp1Handler(ch.pipeline(), sslChannelProvider, sslHelper); + break; + } } else { - handleHttp1(ch, sslChannelProvider); + // No alpn presented or OpenSSL + configureHttp1Pipeline(ch.pipeline(), sslChannelProvider, sslHelper); + configureHttp1Handler(ch.pipeline(), sslChannelProvider, sslHelper); } } else { - handleHttp1(ch, sslChannelProvider); + configureHttp1Pipeline(ch.pipeline(), sslChannelProvider, sslHelper); + configureHttp1Handler(ch.pipeline(), sslChannelProvider, sslHelper); } } else { handleException(future.cause()); @@ -153,7 +162,8 @@ private void configurePipeline(Channel ch, SslChannelProvider sslChannelProvider }); } else { if (disableH2C) { - handleHttp1(ch, sslChannelProvider); + configureHttp1Pipeline(ch.pipeline(), sslChannelProvider, sslHelper); + configureHttp1Handler(ch.pipeline(), sslChannelProvider, sslHelper); } else { IdleStateHandler idle; int idleTimeout = options.getIdleTimeout(); @@ -175,9 +185,10 @@ protected void configure(ChannelHandlerContext ctx, boolean h2c) { pipeline.remove(idle); } if (h2c) { - handleHttp2(ctx.channel()); + configureHttp2(ctx.pipeline()); } else { - handleHttp1(ch, sslChannelProvider); + configureHttp1Pipeline(ctx.pipeline(), sslChannelProvider, sslHelper); + configureHttp1OrH2CUpgradeHandler(ctx.pipeline(), sslChannelProvider, sslHelper); } } @@ -205,10 +216,6 @@ private void handleException(Throwable cause) { context.emit(cause, exceptionHandler); } - private void handleHttp1(Channel ch, SslChannelProvider sslChannelProvider) { - configureHttp1OrH2C(ch.pipeline(), sslChannelProvider); - } - private void sendServiceUnavailable(Channel ch) { ch.writeAndFlush( Unpooled.copiedBuffer("HTTP/1.1 503 Service Unavailable\r\n" + @@ -217,13 +224,17 @@ private void sendServiceUnavailable(Channel ch) { .addListener(ChannelFutureListener.CLOSE); } - private void handleHttp2(Channel ch) { + private void configureHttp2(ChannelPipeline pipeline) { + configureHttp2Handler(pipeline); + configureHttp2Pipeline(pipeline); + } + + private void configureHttp2Handler(ChannelPipeline pipeline) { VertxHttp2ConnectionHandler handler = buildHttp2ConnectionHandler(context, connectionHandler); - ch.pipeline().addLast("handler", handler); - configureHttp2(ch.pipeline()); + pipeline.addLast("handler", handler); } - void configureHttp2(ChannelPipeline pipeline) { + void configureHttp2Pipeline(ChannelPipeline pipeline) { if (!server.requestAccept()) { // That should send an HTTP/2 go away pipeline.channel().close(); @@ -237,11 +248,14 @@ void configureHttp2(ChannelPipeline pipeline) { } } - VertxHttp2ConnectionHandler buildHttp2ConnectionHandler(EventLoopContext ctx, Handler handler_) { + VertxHttp2ConnectionHandler buildHttp2ConnectionHandler(ContextInternal ctx, Handler handler_) { HttpServerMetrics metrics = (HttpServerMetrics) server.getMetrics(); + int maxRstFramesPerWindow = options.getHttp2RstFloodMaxRstFramePerWindow(); + int secondsPerWindow = (int)options.getHttp2RstFloodWindowDurationTimeUnit().toSeconds(options.getHttp2RstFloodWindowDuration()); VertxHttp2ConnectionHandler handler = new VertxHttp2ConnectionHandlerBuilder() .server(true) .useCompression(compressionOptions) + .decoderEnforceMaxRstFramesPerWindow(maxRstFramesPerWindow, secondsPerWindow) .useDecompression(options.isDecompressionSupported()) .initialSettings(options.getInitialSettings()) .connectionFactory(connHandler -> { @@ -262,13 +276,14 @@ VertxHttp2ConnectionHandler buildHttp2ConnectionHandler(E return handler; } - private void configureHttp1OrH2C(ChannelPipeline pipeline, SslChannelProvider sslChannelProvider) { + private void configureHttp1OrH2CUpgradeHandler(ChannelPipeline pipeline, SslChannelProvider sslChannelProvider, SSLHelper sslHelper) { + pipeline.addLast("h2c", new Http1xUpgradeToH2CHandler(this, sslChannelProvider, sslHelper, options.isCompressionSupported(), options.isDecompressionSupported())); + } + + private void configureHttp1Pipeline(ChannelPipeline pipeline, SslChannelProvider sslChannelProvider, SSLHelper sslHelper) { if (logEnabled) { pipeline.addLast("logging", new LoggingHandler(options.getActivityLogDataFormat())); } - if (HttpServerImpl.USE_FLASH_POLICY_HANDLER) { - pipeline.addLast("flashpolicy", new FlashPolicyHandler()); - } pipeline.addLast("httpDecoder", new VertxHttpRequestDecoder(options)); pipeline.addLast("httpEncoder", new VertxHttpResponseEncoder()); if (options.isDecompressionSupported()) { @@ -287,14 +302,9 @@ private void configureHttp1OrH2C(ChannelPipeline pipeline, SslChannelProvider ss if (idleTimeout > 0 || readIdleTimeout > 0 || writeIdleTimeout > 0) { pipeline.addLast("idle", new IdleStateHandler(readIdleTimeout, writeIdleTimeout, idleTimeout, options.getIdleTimeoutUnit())); } - if (disableH2C) { - configureHttp1(pipeline, sslChannelProvider); - } else { - pipeline.addLast("h2c", new Http1xUpgradeToH2CHandler(this, sslChannelProvider, options.isCompressionSupported(), options.isDecompressionSupported())); - } } - void configureHttp1(ChannelPipeline pipeline, SslChannelProvider sslChannelProvider) { + void configureHttp1Handler(ChannelPipeline pipeline, SslChannelProvider sslChannelProvider, SSLHelper sslHelper) { if (!server.requestAccept()) { sendServiceUnavailable(pipeline.channel()); return; @@ -304,6 +314,7 @@ void configureHttp1(ChannelPipeline pipeline, SslChannelProvider sslChannelProvi Http1xServerConnection conn = new Http1xServerConnection( streamContextSupplier, sslChannelProvider, + sslHelper, options, chctx, context, diff --git a/src/main/java/io/vertx/core/http/impl/HttpUtils.java b/src/main/java/io/vertx/core/http/impl/HttpUtils.java index b16e389e1a8..acea7fd50f9 100644 --- a/src/main/java/io/vertx/core/http/impl/HttpUtils.java +++ b/src/main/java/io/vertx/core/http/impl/HttpUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -31,6 +31,8 @@ import io.vertx.core.http.StreamPriority; import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.VertxInternal; +import io.vertx.core.net.HostAndPort; +import io.vertx.core.net.impl.HostAndPortImpl; import io.vertx.core.spi.tracing.TagExtractor; import java.io.File; @@ -68,7 +70,7 @@ public final class HttpUtils { static final TagExtractor SERVER_REQUEST_TAG_EXTRACTOR = new TagExtractor() { @Override public int len(HttpServerRequest req) { - return 2; + return req.query() == null ? 4 : 5; } @Override public String name(HttpServerRequest req, int index) { @@ -77,6 +79,12 @@ public String name(HttpServerRequest req, int index) { return "http.url"; case 1: return "http.method"; + case 2: + return "http.scheme"; + case 3: + return "http.path"; + case 4: + return "http.query"; } throw new IndexOutOfBoundsException("Invalid tag index " + index); } @@ -87,6 +95,12 @@ public String value(HttpServerRequest req, int index) { return req.absoluteURI(); case 1: return req.method().name(); + case 2: + return req.scheme(); + case 3: + return req.path(); + case 4: + return req.query(); } throw new IndexOutOfBoundsException("Invalid tag index " + index); } @@ -507,9 +521,15 @@ static String absoluteURI(String serverOrigin, HttpServerRequest req) throws URI if (scheme != null && (scheme.equals("http") || scheme.equals("https"))) { absoluteURI = uri.toString(); } else { - String host = req.host(); - if (host != null) { - absoluteURI = req.scheme() + "://" + host + uri; + boolean ssl = req.isSSL(); + HostAndPort authority = req.authority(); + if (authority != null) { + StringBuilder sb = new StringBuilder(req.scheme()).append("://").append(authority.host()); + if (authority.port() > 0 && (ssl && authority.port() != 443) || (!ssl && authority.port() != 80)) { + sb.append(':').append(authority.port()); + } + sb.append(uri); + absoluteURI = sb.toString(); } else { // Fall back to the server origin absoluteURI = serverOrigin + uri; @@ -952,4 +972,35 @@ static Future resolveFile(ContextInternal context, String filename, l static boolean isConnectOrUpgrade(io.vertx.core.http.HttpMethod method, MultiMap headers) { return method == io.vertx.core.http.HttpMethod.CONNECT || (method == io.vertx.core.http.HttpMethod.GET && headers.contains(io.vertx.core.http.HttpHeaders.CONNECTION, io.vertx.core.http.HttpHeaders.UPGRADE, true)); } + + static boolean isKeepAlive(HttpRequest request) { + HttpVersion version = request.protocolVersion(); + return (version == HttpVersion.HTTP_1_1 && !request.headers().contains(io.vertx.core.http.HttpHeaders.CONNECTION, io.vertx.core.http.HttpHeaders.CLOSE, true)) + || (version == HttpVersion.HTTP_1_0 && request.headers().contains(io.vertx.core.http.HttpHeaders.CONNECTION, io.vertx.core.http.HttpHeaders.KEEP_ALIVE, true)); + } + + public static boolean isValidHostAuthority(String host) { + int len = host.length(); + return HostAndPortImpl.parseHost(host, 0, len) == len; + } + + public static boolean canUpgradeToWebSocket(HttpServerRequest req) { + if (req.version() != io.vertx.core.http.HttpVersion.HTTP_1_1) { + return false; + } + if (req.method() != io.vertx.core.http.HttpMethod.GET) { + return false; + } + MultiMap headers = req.headers(); + for (String connection : headers.getAll(io.vertx.core.http.HttpHeaders.CONNECTION)) { + if (connection.toLowerCase().contains(io.vertx.core.http.HttpHeaders.UPGRADE)) { + for (String upgrade : headers.getAll(io.vertx.core.http.HttpHeaders.UPGRADE)) { + if (upgrade.toLowerCase().contains(io.vertx.core.http.HttpHeaders.WEBSOCKET)) { + return true; + } + } + } + } + return false; + } } diff --git a/src/main/java/io/vertx/core/http/impl/NettyFileUpload.java b/src/main/java/io/vertx/core/http/impl/NettyFileUpload.java index a81c5f56724..0cd0e7c3905 100644 --- a/src/main/java/io/vertx/core/http/impl/NettyFileUpload.java +++ b/src/main/java/io/vertx/core/http/impl/NettyFileUpload.java @@ -17,6 +17,7 @@ import io.vertx.core.Context; import io.vertx.core.Handler; import io.vertx.core.buffer.Buffer; +import io.vertx.core.buffer.impl.BufferInternal; import io.vertx.core.http.HttpServerRequest; import io.vertx.core.streams.ReadStream; import io.vertx.core.streams.impl.InboundBuffer; @@ -138,13 +139,13 @@ public void handleException(Throwable err) { @Override public void setContent(ByteBuf channelBuffer) throws IOException { completed = true; - receiveData(Buffer.buffer(channelBuffer)); + receiveData(BufferInternal.buffer(channelBuffer)); end(); } @Override public void addContent(ByteBuf channelBuffer, boolean last) throws IOException { - receiveData(Buffer.buffer(channelBuffer)); + receiveData(BufferInternal.buffer(channelBuffer)); if (last) { completed = true; end(); diff --git a/src/main/java/io/vertx/core/http/impl/ServerWebSocketImpl.java b/src/main/java/io/vertx/core/http/impl/ServerWebSocketImpl.java index 74d8ccf9ffe..d5a6c1eccd9 100644 --- a/src/main/java/io/vertx/core/http/impl/ServerWebSocketImpl.java +++ b/src/main/java/io/vertx/core/http/impl/ServerWebSocketImpl.java @@ -16,10 +16,12 @@ import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker; +import io.vertx.codegen.annotations.Nullable; import io.vertx.core.*; import io.vertx.core.http.ServerWebSocket; import io.vertx.core.http.WebSocketFrame; import io.vertx.core.impl.ContextInternal; +import io.vertx.core.net.HostAndPort; import io.vertx.core.spi.metrics.HttpServerMetrics; import static io.netty.handler.codec.http.HttpResponseStatus.*; @@ -40,7 +42,7 @@ public class ServerWebSocketImpl extends WebSocketImplBase private final Http1xServerConnection conn; private final long closingTimeoutMS; private final String scheme; - private final String host; + private final HostAndPort authority; private final String uri; private final String path; private final String query; @@ -62,7 +64,7 @@ public class ServerWebSocketImpl extends WebSocketImplBase this.conn = conn; this.closingTimeoutMS = closingTimeout >= 0 ? closingTimeout * 1000L : -1L; this.scheme = request.scheme(); - this.host = request.host(); + this.authority = request.authority(); this.uri = request.uri(); this.path = request.path(); this.query = request.query(); @@ -76,8 +78,8 @@ public String scheme() { } @Override - public String host() { - return host; + public HostAndPort authority() { + return authority; } @Override diff --git a/src/main/java/io/vertx/core/http/impl/SharedClientHttpStreamEndpoint.java b/src/main/java/io/vertx/core/http/impl/SharedClientHttpStreamEndpoint.java index 631a21ffb18..2541abc3277 100644 --- a/src/main/java/io/vertx/core/http/impl/SharedClientHttpStreamEndpoint.java +++ b/src/main/java/io/vertx/core/http/impl/SharedClientHttpStreamEndpoint.java @@ -17,7 +17,7 @@ import io.vertx.core.http.HttpConnection; import io.vertx.core.http.HttpVersion; import io.vertx.core.impl.ContextInternal; -import io.vertx.core.impl.EventLoopContext; +import io.vertx.core.impl.NoStackTraceTimeoutException; import io.vertx.core.impl.future.PromiseInternal; import io.vertx.core.net.impl.pool.ConnectResult; import io.vertx.core.net.impl.pool.ConnectionPool; @@ -80,7 +80,7 @@ public SharedClientHttpStreamEndpoint(HttpClientImpl client, } @Override - public Future> connect(EventLoopContext context, Listener listener) { + public Future> connect(ContextInternal context, Listener listener) { return connector .httpConnect(context) .map(connection -> { diff --git a/src/main/java/io/vertx/core/http/impl/StatisticsGatheringHttpClientStream.java b/src/main/java/io/vertx/core/http/impl/StatisticsGatheringHttpClientStream.java index ea53da5e137..3e540249432 100644 --- a/src/main/java/io/vertx/core/http/impl/StatisticsGatheringHttpClientStream.java +++ b/src/main/java/io/vertx/core/http/impl/StatisticsGatheringHttpClientStream.java @@ -21,8 +21,7 @@ import io.vertx.core.http.HttpVersion; import io.vertx.core.http.StreamPriority; import io.vertx.core.impl.ContextInternal; -import io.vertx.core.net.SocketAddress; -import io.vertx.core.spi.resolver.AddressResolver; +import io.vertx.core.spi.resolver.endpoint.EndpointRequest; import io.vertx.core.streams.WriteStream; /** @@ -30,19 +29,14 @@ * * @author Julien Viet */ -class StatisticsGatheringHttpClientStream implements HttpClientStream { +class StatisticsGatheringHttpClientStream implements HttpClientStream { private final HttpClientStream delegate; - private final AddressResolver resolver; - private final S state; - private final SocketAddress server; - private M metric; + private final EndpointRequest endpointRequest; - StatisticsGatheringHttpClientStream(HttpClientStream delegate, AddressResolver resolver, S state, SocketAddress server) { + StatisticsGatheringHttpClientStream(HttpClientStream delegate, EndpointRequest endpointRequest) { this.delegate = delegate; - this.resolver = resolver; - this.state = state; - this.server = server; + this.endpointRequest = endpointRequest; } @Override @@ -77,9 +71,9 @@ public ContextInternal getContext() { @Override public Future writeHead(HttpRequestHead request, boolean chunked, ByteBuf buf, boolean end, StreamPriority priority, boolean connect) { - metric = resolver.requestBegin(state, server); + endpointRequest.reportRequestBegin(); if (end) { - resolver.requestEnd(metric); + endpointRequest.reportRequestEnd(); } return delegate.writeHead(request, chunked, buf, end, priority, connect); } @@ -87,14 +81,14 @@ public Future writeHead(HttpRequestHead request, boolean chunked, ByteBuf @Override public Future writeBuffer(ByteBuf buf, boolean end) { if (end) { - resolver.requestEnd(metric); + endpointRequest.reportRequestEnd(); } return delegate.writeBuffer(buf, end); } @Override - public void writeFrame(int type, int flags, ByteBuf payload) { - delegate.writeFrame(type, flags, payload); + public Future writeFrame(int type, int flags, ByteBuf payload) { + return delegate.writeFrame(type, flags, payload); } @Override @@ -121,7 +115,7 @@ public void unknownFrameHandler(Handler handler) { public void headHandler(Handler handler) { if (handler != null) { delegate.headHandler(multimap -> { - resolver.responseBegin(metric); + endpointRequest.reportResponseBegin(); handler.handle(multimap); }); } else { @@ -138,7 +132,7 @@ public void chunkHandler(Handler handler) { public void endHandler(Handler handler) { if (handler != null) { delegate.endHandler(multimap -> { - resolver.responseEnd(metric); + endpointRequest.reportResponseEnd(); handler.handle(multimap); }); } else { @@ -193,7 +187,15 @@ public void updatePriority(StreamPriority streamPriority) { @Override public WriteStream exceptionHandler(@Nullable Handler handler) { - return delegate.exceptionHandler(handler); + if (handler != null) { + delegate.exceptionHandler(err -> { + endpointRequest.reportFailure(err); + handler.handle(err); + }); + } else { + delegate.exceptionHandler(null); + } + return this; } @Override diff --git a/src/main/java/io/vertx/core/http/impl/VertxHttp2ConnectionHandler.java b/src/main/java/io/vertx/core/http/impl/VertxHttp2ConnectionHandler.java index ee1ff0b0011..8834d8209d3 100644 --- a/src/main/java/io/vertx/core/http/impl/VertxHttp2ConnectionHandler.java +++ b/src/main/java/io/vertx/core/http/impl/VertxHttp2ConnectionHandler.java @@ -24,6 +24,7 @@ import io.netty.util.concurrent.Promise; import io.vertx.core.Handler; import io.vertx.core.buffer.Buffer; +import io.vertx.core.buffer.impl.BufferInternal; import io.vertx.core.http.GoAway; import io.vertx.core.net.impl.ShutdownEvent; import io.vertx.core.net.impl.ConnectionBase; @@ -210,29 +211,32 @@ public void onStreamRemoved(Http2Stream stream) { @Override public void onGoAwaySent(int lastStreamId, long errorCode, ByteBuf debugData) { - connection.onGoAwaySent(new GoAway().setErrorCode(errorCode).setLastStreamId(lastStreamId).setDebugData(Buffer.buffer(debugData))); + connection.onGoAwaySent(new GoAway().setErrorCode(errorCode).setLastStreamId(lastStreamId).setDebugData(BufferInternal.buffer(debugData))); } @Override public void onGoAwayReceived(int lastStreamId, long errorCode, ByteBuf debugData) { - connection.onGoAwayReceived(new GoAway().setErrorCode(errorCode).setLastStreamId(lastStreamId).setDebugData(Buffer.buffer(debugData))); + connection.onGoAwayReceived(new GoAway().setErrorCode(errorCode).setLastStreamId(lastStreamId).setDebugData(BufferInternal.buffer(debugData))); } // - void writeHeaders(Http2Stream stream, Http2Headers headers, boolean end, int streamDependency, short weight, boolean exclusive, FutureListener listener) { + void writeHeaders(Http2Stream stream, Http2Headers headers, boolean end, int streamDependency, short weight, boolean exclusive, boolean checkFlush, FutureListener listener) { ChannelPromise promise = listener == null ? chctx.voidPromise() : chctx.newPromise().addListener(listener); encoder().writeHeaders(chctx, stream.id(), headers, streamDependency, weight, exclusive, 0, end, promise); - checkFlush(); + if (checkFlush) { + checkFlush(); + } } void writeData(Http2Stream stream, ByteBuf chunk, boolean end, FutureListener listener) { ChannelPromise promise = listener == null ? chctx.voidPromise() : chctx.newPromise().addListener(listener); - encoder().writeData(chctx, stream.id(), chunk, 0, end, promise); - Http2RemoteFlowController controller = encoder().flowController(); + Http2ConnectionEncoder encoder = encoder(); + encoder.writeData(chctx, stream.id(), chunk, 0, end, promise); + Http2RemoteFlowController controller = encoder.flowController(); if (!controller.isWritable(stream) || end) { try { - encoder().flowController().writePendingBytes(); + encoder.flowController().writePendingBytes(); } catch (Http2Exception e) { onError(chctx, true, e); } @@ -278,8 +282,9 @@ void consume(Http2Stream stream, int numBytes) { } } - void writeFrame(Http2Stream stream, byte type, short flags, ByteBuf payload) { - encoder().writeFrame(chctx, type, stream.id(), new Http2Flags(flags), payload, chctx.newPromise()); + void writeFrame(Http2Stream stream, byte type, short flags, ByteBuf payload, FutureListener listener) { + ChannelPromise promise = listener == null ? chctx.voidPromise() : chctx.newPromise().addListener(listener); + encoder().writeFrame(chctx, type, stream.id(), new Http2Flags(flags), payload, promise); checkFlush(); } @@ -411,9 +416,6 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception connection.onHeadersRead(ctx, 1, frame.headers(), frame.padding(), frame.isEndStream()); } else if (msg instanceof Http2DataFrame) { Http2DataFrame frame = (Http2DataFrame) msg; - Http2LocalFlowController controller = decoder().flowController(); - Http2Stream stream = decoder().connection().stream(1); - controller.receiveFlowControlledFrame(stream, frame.content(), frame.padding(), frame.isEndStream()); connection.onDataRead(ctx, 1, frame.content(), frame.padding(), frame.isEndStream()); } } else { diff --git a/src/main/java/io/vertx/core/http/impl/VertxHttp2ConnectionHandlerBuilder.java b/src/main/java/io/vertx/core/http/impl/VertxHttp2ConnectionHandlerBuilder.java index 98239ee14a5..f36a6d3dcfe 100644 --- a/src/main/java/io/vertx/core/http/impl/VertxHttp2ConnectionHandlerBuilder.java +++ b/src/main/java/io/vertx/core/http/impl/VertxHttp2ConnectionHandlerBuilder.java @@ -14,18 +14,8 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.compression.CompressionOptions; -import io.netty.handler.codec.http2.AbstractHttp2ConnectionHandlerBuilder; -import io.netty.handler.codec.http2.CompressorHttp2ConnectionEncoder; -import io.netty.handler.codec.http2.Http2ConnectionDecoder; -import io.netty.handler.codec.http2.Http2ConnectionEncoder; -import io.netty.handler.codec.http2.Http2Exception; -import io.netty.handler.codec.http2.Http2Flags; -import io.netty.handler.codec.http2.Http2FrameListener; -import io.netty.handler.codec.http2.Http2FrameLogger; -import io.netty.handler.codec.http2.Http2Headers; -import io.netty.handler.codec.http2.Http2Settings; +import io.netty.handler.codec.http2.*; import io.netty.handler.logging.LogLevel; -import io.vertx.core.http.HttpServerOptions; import java.util.function.Function; @@ -40,13 +30,15 @@ class VertxHttp2ConnectionHandlerBuilder extends private CompressionOptions[] compressionOptions; private Function, C> connectionFactory; private boolean logEnabled; + private boolean server; protected VertxHttp2ConnectionHandlerBuilder server(boolean isServer) { - return super.server(isServer); + this.server = isServer; + return this; } VertxHttp2ConnectionHandlerBuilder initialSettings(io.vertx.core.http.Http2Settings settings) { - HttpUtils.fromVertxInitialSettings(isServer(), settings, initialSettings()); + HttpUtils.fromVertxInitialSettings(server, settings, initialSettings()); return this; } @@ -55,6 +47,11 @@ VertxHttp2ConnectionHandlerBuilder useCompression(CompressionOptions[] compre return this; } + @Override + protected VertxHttp2ConnectionHandlerBuilder decoderEnforceMaxRstFramesPerWindow(int maxRstFramesPerWindow, int secondsPerWindow) { + return super.decoderEnforceMaxRstFramesPerWindow(maxRstFramesPerWindow, secondsPerWindow); + } + @Override protected VertxHttp2ConnectionHandlerBuilder gracefulShutdownTimeoutMillis(long gracefulShutdownTimeoutMillis) { return super.gracefulShutdownTimeoutMillis(gracefulShutdownTimeoutMillis); @@ -80,6 +77,7 @@ protected VertxHttp2ConnectionHandler build() { if (logEnabled) { frameLogger(new Http2FrameLogger(LogLevel.DEBUG)); } + configureStreamByteDistributor(); // Make this damn builder happy frameListener(new Http2FrameListener() { @Override @@ -138,9 +136,16 @@ public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int stream return super.build(); } + private void configureStreamByteDistributor() { + DefaultHttp2Connection conn = new DefaultHttp2Connection(server, maxReservedStreams()); + StreamByteDistributor distributor = new UniformStreamByteDistributor(conn); + conn.remote().flowController(new DefaultHttp2RemoteFlowController(conn, distributor)); + connection(conn); + } + @Override protected VertxHttp2ConnectionHandler build(Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder, Http2Settings initialSettings) throws Exception { - if (isServer()) { + if (server) { if (compressionOptions != null) { encoder = new CompressorHttp2ConnectionEncoder(encoder, compressionOptions); } diff --git a/src/main/java/io/vertx/core/http/impl/VertxHttp2Stream.java b/src/main/java/io/vertx/core/http/impl/VertxHttp2Stream.java index 318b2f5c719..838fdc8f5c3 100644 --- a/src/main/java/io/vertx/core/http/impl/VertxHttp2Stream.java +++ b/src/main/java/io/vertx/core/http/impl/VertxHttp2Stream.java @@ -18,6 +18,7 @@ import io.netty.handler.codec.http2.Http2Headers; import io.netty.handler.codec.http2.Http2Stream; import io.netty.util.concurrent.FutureListener; +import io.vertx.core.Future; import io.vertx.core.MultiMap; import io.vertx.core.Promise; import io.vertx.core.buffer.Buffer; @@ -26,6 +27,8 @@ import io.vertx.core.http.impl.headers.Http2HeadersAdaptor; import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.VertxInternal; +import io.vertx.core.net.impl.OutboundMessageQueue; +import io.vertx.core.net.impl.MessageWrite; import io.vertx.core.streams.impl.InboundBuffer; /** @@ -35,15 +38,16 @@ abstract class VertxHttp2Stream { private static final MultiMap EMPTY = new Http2HeadersAdaptor(EmptyHttp2Headers.INSTANCE); + protected final OutboundMessageQueue messageQueue; protected final C conn; protected final VertxInternal vertx; protected final ContextInternal context; protected Http2Stream stream; // Client context + private boolean writable; private StreamPriority priority; private final InboundBuffer pending; - private boolean writable; private long bytesRead; private long bytesWritten; protected boolean isConnect; @@ -54,8 +58,24 @@ abstract class VertxHttp2Stream { this.context = context; this.pending = new InboundBuffer<>(context, 5); this.priority = HttpUtils.DEFAULT_STREAM_PRIORITY; - this.writable = true; this.isConnect = false; + this.writable = true; + this.messageQueue = new OutboundMessageQueue<>(conn.getContext().nettyEventLoop()) { + // TODO implement stop drain to optimize flushes ? + @Override + public boolean test(MessageWrite write) { + if (writable) { + write.write(); + return true; + } else { + return false; + } + } + @Override + protected void writeQueueDrained() { + context.emit(VertxHttp2Stream.this, VertxHttp2Stream::handleWriteQueueDrained); + } + }; pending.handler(item -> { if (item instanceof MultiMap) { handleEnd((MultiMap) item); @@ -80,8 +100,8 @@ abstract class VertxHttp2Stream { void init(Http2Stream stream) { synchronized (this) { this.stream = stream; - this.writable = this.conn.handler.encoder().flowController().isWritable(stream); } + writable = this.conn.handler.encoder().flowController().isWritable(stream); stream.setProperty(conn.streamKey, this); } @@ -120,14 +140,10 @@ void onData(Buffer data) { } void onWritabilityChanged() { - context.emit(null, v -> { - boolean w; - synchronized (VertxHttp2Stream.this) { - writable = !writable; - w = writable; - } - handleWritabilityChanged(w); - }); + writable = !writable; + if (writable) { + messageQueue.drain(); + } } void onEnd() { @@ -159,34 +175,58 @@ public void doFetch(long amount) { pending.fetch(amount); } - public synchronized boolean isNotWritable() { - return !writable; + public boolean isNotWritable() { + return !messageQueue.isWritable(); } - public final void writeFrame(int type, int flags, ByteBuf payload) { + public final Future writeFrame(int type, int flags, ByteBuf payload) { + Promise promise = context.promise(); EventLoop eventLoop = conn.getContext().nettyEventLoop(); if (eventLoop.inEventLoop()) { - doWriteFrame(type, flags, payload); + doWriteFrame(type, flags, payload, promise); } else { - eventLoop.execute(() -> doWriteFrame(type, flags, payload)); + eventLoop.execute(() -> doWriteFrame(type, flags, payload, promise)); } + return promise.future(); } - private void doWriteFrame(int type, int flags, ByteBuf payload) { - conn.handler.writeFrame(stream, (byte) type, (short) flags, payload); - } - - final void writeHeaders(Http2Headers headers, boolean end, Promise promise) { + public final void writeFrame(int type, int flags, ByteBuf payload, Promise promise) { EventLoop eventLoop = conn.getContext().nettyEventLoop(); if (eventLoop.inEventLoop()) { - doWriteHeaders(headers, end, promise); + doWriteFrame(type, flags, payload, promise); } else { - eventLoop.execute(() -> doWriteHeaders(headers, end, promise)); + eventLoop.execute(() -> doWriteFrame(type, flags, payload, promise)); } } - void doWriteHeaders(Http2Headers headers, boolean end, Promise promise) { - conn.handler.writeHeaders(stream, headers, end, priority.getDependency(), priority.getWeight(), priority.isExclusive(), (FutureListener) promise); + private void doWriteFrame(int type, int flags, ByteBuf payload, Promise promise) { + conn.handler.writeFrame(stream, (byte) type, (short) flags, payload, (FutureListener) promise); + } + + final void writeHeaders(Http2Headers headers, boolean first, boolean end, boolean checkFlush, Promise promise) { + if (first) { + EventLoop eventLoop = conn.getContext().nettyEventLoop(); + if (eventLoop.inEventLoop()) { + doWriteHeaders(headers, end, checkFlush, promise); + } else { + eventLoop.execute(() -> doWriteHeaders(headers, end, checkFlush, promise)); + } + } else { + messageQueue.write(new MessageWrite() { + @Override + public void write() { + doWriteHeaders(headers, end, checkFlush, promise); + } + @Override + public void cancel(Throwable cause) { + promise.fail(cause); + } + }); + } + } + + void doWriteHeaders(Http2Headers headers, boolean end, boolean checkFlush, Promise promise) { + conn.handler.writeHeaders(stream, headers, end, priority.getDependency(), priority.getWeight(), priority.isExclusive(), checkFlush, (FutureListener) promise); if (end) { endWritten(); } @@ -200,13 +240,16 @@ private void writePriorityFrame(StreamPriority priority) { } final void writeData(ByteBuf chunk, boolean end, Promise promise) { - ContextInternal ctx = conn.getContext(); - EventLoop eventLoop = ctx.nettyEventLoop(); - if (eventLoop.inEventLoop()) { - doWriteData(chunk, end, promise); - } else { - eventLoop.execute(() -> doWriteData(chunk, end, promise)); - } + messageQueue.write(new MessageWrite() { + @Override + public void write() { + doWriteData(chunk, end, promise); + } + @Override + public void cancel(Throwable cause) { + promise.fail(cause); + } + }); } void doWriteData(ByteBuf buf, boolean end, Promise promise) { @@ -247,7 +290,7 @@ protected void doWriteReset(long code) { } } - void handleWritabilityChanged(boolean writable) { + void handleWriteQueueDrained() { } void handleData(Buffer buf) { diff --git a/src/main/java/io/vertx/core/http/impl/WebSocketClientImpl.java b/src/main/java/io/vertx/core/http/impl/WebSocketClientImpl.java new file mode 100644 index 00000000000..89b0c5bedfe --- /dev/null +++ b/src/main/java/io/vertx/core/http/impl/WebSocketClientImpl.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.http.impl; + +import io.vertx.core.Future; +import io.vertx.core.MultiMap; +import io.vertx.core.Promise; +import io.vertx.core.http.*; +import io.vertx.core.impl.ContextInternal; +import io.vertx.core.impl.VertxInternal; +import io.vertx.core.impl.future.PromiseInternal; +import io.vertx.core.net.ClientSSLOptions; +import io.vertx.core.net.HostAndPort; +import io.vertx.core.net.ProxyOptions; +import io.vertx.core.net.SocketAddress; +import io.vertx.core.net.impl.endpoint.EndpointManager; +import io.vertx.core.net.impl.endpoint.EndpointProvider; +import io.vertx.core.spi.metrics.ClientMetrics; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +public class WebSocketClientImpl extends HttpClientBase implements WebSocketClient { + + private final WebSocketClientOptions options; + private final EndpointManager webSocketCM; + + public WebSocketClientImpl(VertxInternal vertx, HttpClientOptions options, WebSocketClientOptions wsOptions) { + super(vertx, options); + + this.options = wsOptions; + this.webSocketCM = webSocketConnectionManager(); + } + + private EndpointManager webSocketConnectionManager() { + return new EndpointManager<>(); + } + + protected void doShutdown(Promise p) { + webSocketCM.shutdown(); + super.doShutdown(p); + } + + protected void doClose(Promise p) { + webSocketCM.close(); + super.doClose(p); + } + + @Override + public Future connect(WebSocketConnectOptions options) { + return webSocket(options); + } + + void webSocket(ContextInternal ctx, WebSocketConnectOptions connectOptions, Promise promise) { + int port = getPort(connectOptions); + String host = getHost(connectOptions); + SocketAddress addr = SocketAddress.inetSocketAddress(port, host); + HostAndPort peer = HostAndPort.create(host, port); + ProxyOptions proxyOptions = computeProxyOptions(connectOptions.getProxyOptions(), addr); + ClientSSLOptions sslOptions = sslOptions(connectOptions); + EndpointKey key = new EndpointKey(connectOptions.isSsl() != null ? connectOptions.isSsl() : options.isSsl(), sslOptions, proxyOptions, addr, peer); + // todo: cache + EndpointProvider provider = (key_, dispose) -> { + int maxPoolSize = options.getMaxConnections(); + ClientMetrics metrics = WebSocketClientImpl.this.metrics != null ? WebSocketClientImpl.this.metrics.createEndpointMetrics(key_.server, maxPoolSize) : null; + HttpChannelConnector connector = new HttpChannelConnector(WebSocketClientImpl.this, netClient, sslOptions, key_.proxyOptions, metrics, HttpVersion.HTTP_1_1, key_.ssl, false, key_.authority, key_.server); + return new WebSocketEndpoint(null, maxPoolSize, connector, dispose); + }; + webSocketCM + .withEndpointAsync(key, provider, (endpoint, created) -> endpoint.requestConnection(ctx, 0L)) + .onComplete(c -> { + if (c.succeeded()) { + long timeout = Math.max(connectOptions.getTimeout(), 0L); + if (connectOptions.getIdleTimeout() >= 0L) { + timeout = connectOptions.getIdleTimeout(); + } + Http1xClientConnection conn = (Http1xClientConnection) c.result(); + conn.toWebSocket( + ctx, + connectOptions.getURI(), + connectOptions.getHeaders(), + connectOptions.getAllowOriginHeader(), + options, + connectOptions.getVersion(), + connectOptions.getSubProtocols(), + timeout, + connectOptions.isRegisterWriteHandlers(), + WebSocketClientImpl.this.options.getMaxFrameSize(), + promise); + } else { + promise.fail(c.cause()); + } + }); + } + + public Future webSocket(int port, String host, String requestURI) { + return webSocket(new WebSocketConnectOptions().setURI(requestURI).setHost(host).setPort(port)); + } + + public Future webSocket(WebSocketConnectOptions options) { + return webSocket(vertx.getOrCreateContext(), options); + } + + static WebSocketConnectOptions webSocketConnectOptionsAbs(String url, MultiMap headers, WebsocketVersion version, List subProtocols) { + URI uri; + try { + uri = new URI(url); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } + String scheme = uri.getScheme(); + if (!"ws".equals(scheme) && !"wss".equals(scheme)) { + throw new IllegalArgumentException("Scheme: " + scheme); + } + boolean ssl = scheme.length() == 3; + int port = uri.getPort(); + if (port == -1) port = ssl ? 443 : 80; + StringBuilder relativeUri = new StringBuilder(); + if (uri.getRawPath() != null) { + relativeUri.append(uri.getRawPath()); + } + if (uri.getRawQuery() != null) { + relativeUri.append('?').append(uri.getRawQuery()); + } + if (uri.getRawFragment() != null) { + relativeUri.append('#').append(uri.getRawFragment()); + } + return new WebSocketConnectOptions() + .setHost(uri.getHost()) + .setPort(port).setSsl(ssl) + .setURI(relativeUri.toString()) + .setHeaders(headers) + .setVersion(version) + .setSubProtocols(subProtocols); + } + + public Future webSocketAbs(String url, MultiMap headers, WebsocketVersion version, List subProtocols) { + return webSocket(webSocketConnectOptionsAbs(url, headers, version, subProtocols)); + } + + Future webSocket(ContextInternal ctx, WebSocketConnectOptions connectOptions) { + PromiseInternal promise = ctx.promise(); + webSocket(ctx, connectOptions, promise); + return promise.andThen(ar -> { + if (ar.succeeded()) { + ar.result().resume(); + } + }); + } + + public ClientWebSocket webSocket() { + return new ClientWebSocketImpl(this); + } +} diff --git a/src/main/java/io/vertx/core/http/impl/WebSocketEndpoint.java b/src/main/java/io/vertx/core/http/impl/WebSocketEndpoint.java index 0f9addab93f..fcfb42ca192 100644 --- a/src/main/java/io/vertx/core/http/impl/WebSocketEndpoint.java +++ b/src/main/java/io/vertx/core/http/impl/WebSocketEndpoint.java @@ -11,7 +11,6 @@ package io.vertx.core.http.impl; import io.vertx.core.*; -import io.vertx.core.impl.EventLoopContext; import io.vertx.core.impl.ContextInternal; import io.vertx.core.spi.metrics.ClientMetrics; @@ -61,9 +60,9 @@ private void onEvict() { } private Future tryConnect(ContextInternal ctx) { - EventLoopContext eventLoopContext; - if (ctx instanceof EventLoopContext) { - eventLoopContext = (EventLoopContext) ctx; + ContextInternal eventLoopContext; + if (ctx.isEventLoopContext()) { + eventLoopContext = ctx; } else { eventLoopContext = ctx.owner().createEventLoopContext(ctx.nettyEventLoop(), ctx.workerPool(), ctx.classLoader()); } diff --git a/src/main/java/io/vertx/core/http/impl/WebSocketHandshakeInboundHandler.java b/src/main/java/io/vertx/core/http/impl/WebSocketHandshakeInboundHandler.java index 89d57a4f330..2a7c0b0fc6a 100644 --- a/src/main/java/io/vertx/core/http/impl/WebSocketHandshakeInboundHandler.java +++ b/src/main/java/io/vertx/core/http/impl/WebSocketHandshakeInboundHandler.java @@ -25,6 +25,7 @@ import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.concurrent.Promise; import io.vertx.core.buffer.Buffer; +import io.vertx.core.buffer.impl.BufferInternal; import io.vertx.core.http.UpgradeRejectedException; import io.vertx.core.http.impl.headers.HeadersAdaptor; @@ -112,7 +113,7 @@ private HttpHeaders handshakeComplete(FullHttpResponse response) throws UpgradeR msg, sc, new HeadersAdaptor(response.headers()), - content != null ? Buffer.buffer(content) : null); + content != null ? BufferInternal.buffer(content) : null); } else { handshaker.finishHandshake(chctx.channel(), response); return response.headers(); diff --git a/src/main/java/io/vertx/core/http/impl/WebSocketImplBase.java b/src/main/java/io/vertx/core/http/impl/WebSocketImplBase.java index 06510b8e513..44b22fe3594 100644 --- a/src/main/java/io/vertx/core/http/impl/WebSocketImplBase.java +++ b/src/main/java/io/vertx/core/http/impl/WebSocketImplBase.java @@ -20,20 +20,15 @@ import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; -import io.vertx.codegen.annotations.Nullable; -import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.MultiMap; -import io.vertx.core.Promise; import io.vertx.core.buffer.Buffer; +import io.vertx.core.buffer.impl.BufferInternal; import io.vertx.core.eventbus.EventBus; import io.vertx.core.eventbus.Message; import io.vertx.core.eventbus.MessageConsumer; -import io.vertx.core.http.HttpConnection; -import io.vertx.core.http.WebSocketBase; -import io.vertx.core.http.WebSocketFrame; -import io.vertx.core.http.WebSocketFrameType; +import io.vertx.core.http.*; import io.vertx.core.http.impl.ws.WebSocketFrameImpl; import io.vertx.core.http.impl.ws.WebSocketFrameInternal; import io.vertx.core.impl.ContextInternal; @@ -59,7 +54,7 @@ * @author Tim Fox * @param self return type */ -public abstract class WebSocketImplBase implements WebSocketInternal { +public abstract class WebSocketImplBase implements WebSocketInternal { private final boolean supportsContinuation; private final String textHandlerID; @@ -75,13 +70,12 @@ public abstract class WebSocketImplBase implements WebS private Object metric; private Handler handler; private Handler frameHandler; + private FrameAggregator frameAggregator; private Handler pongHandler; private Handler drainHandler; - private Handler exceptionHandler; private Handler closeHandler; private Handler endHandler; protected final Http1xConnectionBase conn; - private boolean writable; private boolean closed; private Short closeStatusCode; private String closeReason; @@ -107,7 +101,6 @@ public abstract class WebSocketImplBase implements WebS this.maxWebSocketFrameSize = maxWebSocketFrameSize; this.maxWebSocketMessageSize = maxWebSocketMessageSize; this.pending = new InboundBuffer<>(context); - this.writable = !conn.isNotWritable(); this.chctx = conn.channelHandlerContext(); this.headers = headers; @@ -145,7 +138,7 @@ public String textHandlerID() { public boolean writeQueueFull() { synchronized (conn) { checkClosed(); - return conn.isNotWritable(); + return conn.writeQueueFull(); } } @@ -307,7 +300,7 @@ private Future writePartialMessage(WebSocketFrameType frameType, Buffer da Buffer slice = data.slice(offset, end); WebSocketFrame frame; if (offset == 0 || !supportsContinuation) { - frame = new WebSocketFrameImpl(frameType, slice.getByteBuf(), isFinal); + frame = new WebSocketFrameImpl(frameType, ((BufferInternal)slice).getByteBuf(), isFinal); } else { frame = WebSocketFrame.continuationFrame(slice, isFinal); } @@ -333,7 +326,7 @@ public Future writeFrame(WebSocketFrame frame) { } private void writeBinaryFrameInternal(Buffer data) { - writeFrame(new WebSocketFrameImpl(WebSocketFrameType.BINARY, data.getByteBuf())); + writeFrame(new WebSocketFrameImpl(WebSocketFrameType.BINARY, ((BufferInternal)data).getByteBuf())); } private void writeTextFrameInternal(String str) { @@ -418,13 +411,12 @@ protected void handleClose(boolean graceful) { Handler exceptionHandler; synchronized (conn) { closeHandler = this.closeHandler; - exceptionHandler = this.exceptionHandler; + exceptionHandler = conn::handleException; binaryConsumer = this.binaryHandlerRegistration; textConsumer = this.textHandlerRegistration; this.binaryHandlerRegistration = null; this.textHandlerRegistration = null; this.closeHandler = null; - this.exceptionHandler = null; } if (binaryConsumer != null) { binaryConsumer.unregister(); @@ -441,9 +433,14 @@ protected void handleClose(boolean graceful) { } private void receiveFrame(WebSocketFrameInternal frame) { + Handler frameAggregator; Handler frameHandler; synchronized (conn) { frameHandler = this.frameHandler; + frameAggregator = this.frameAggregator; + } + if (frameAggregator != null) { + context.dispatch(frame, frameAggregator); } if (frameHandler != null) { context.dispatch(frame, frameHandler); @@ -525,7 +522,7 @@ public void handle(WebSocketFrameInternal frame) { } private void handleTextFrame(WebSocketFrameInternal frame) { - Buffer frameBuffer = Buffer.buffer(frame.getBinaryData()); + Buffer frameBuffer = BufferInternal.buffer(frame.getBinaryData()); if (textMessageBuffer == null) { textMessageBuffer = frameBuffer; } else { @@ -549,7 +546,7 @@ private void handleTextFrame(WebSocketFrameInternal frame) { } private void handleBinaryFrame(WebSocketFrameInternal frame) { - Buffer frameBuffer = Buffer.buffer(frame.getBinaryData()); + Buffer frameBuffer = BufferInternal.buffer(frame.getBinaryData()); if (binaryMessageBuffer == null) { binaryMessageBuffer = frameBuffer; } else { @@ -583,13 +580,24 @@ public S frameHandler(Handler handler) { } @Override - public WebSocketBase textMessageHandler(Handler handler) { + public WebSocket textMessageHandler(Handler handler) { synchronized (conn) { checkClosed(); - if (frameHandler == null || frameHandler.getClass() != FrameAggregator.class) { - frameHandler = new FrameAggregator(); + if (handler != null) { + if (frameAggregator == null) { + frameAggregator = new FrameAggregator(); + } + frameAggregator.textMessageHandler = handler; + } else { + if (frameAggregator != null) { + if (frameAggregator.binaryMessageHandler == null) { + frameAggregator = null; + } else { + frameAggregator.textMessageHandler = null; + frameAggregator.textMessageBuffer = null; + } + } } - ((FrameAggregator) frameHandler).textMessageHandler = handler; return this; } } @@ -598,16 +606,27 @@ public WebSocketBase textMessageHandler(Handler handler) { public S binaryMessageHandler(Handler handler) { synchronized (conn) { checkClosed(); - if (frameHandler == null || frameHandler.getClass() != FrameAggregator.class) { - frameHandler = new FrameAggregator(); + if (handler != null) { + if (frameAggregator == null) { + frameAggregator = new FrameAggregator(); + } + frameAggregator.binaryMessageHandler = handler; + } else { + if (frameAggregator != null) { + if (frameAggregator.textMessageHandler == null) { + frameAggregator = null; + } else { + frameAggregator.binaryMessageHandler = null; + frameAggregator.binaryMessageBuffer = null; + } + } } - ((FrameAggregator) frameHandler).binaryMessageHandler = handler; return (S) this; } } @Override - public WebSocketBase pongHandler(Handler handler) { + public WebSocket pongHandler(Handler handler) { synchronized (conn) { checkClosed(); this.pongHandler = handler; @@ -621,28 +640,16 @@ private Handler pongHandler() { } } - void handleWritabilityChanged(boolean writable) { + void handleWriteQueueDrained(Void v) { Handler handler; synchronized (conn) { - boolean skip = this.writable && !writable; - this.writable = writable; handler = drainHandler; - if (handler == null || skip) { - return; - } } - context.dispatch(null, handler); + context.dispatch(handler); } void handleException(Throwable t) { - Handler handler; - synchronized (conn) { - handler = this.exceptionHandler; - if (handler == null) { - return; - } - } - context.dispatch(t, handler); + conn.handleException(t); } void handleConnectionClosed() { @@ -702,13 +709,8 @@ private Handler endHandler() { @Override public S exceptionHandler(Handler handler) { - synchronized (conn) { - if (handler != null) { - checkClosed(); - } - this.exceptionHandler = handler; - return (S) this; - } + conn.exceptionHandler(handler); + return (S) this; } @Override diff --git a/src/main/java/io/vertx/core/http/impl/WebSocketInternal.java b/src/main/java/io/vertx/core/http/impl/WebSocketInternal.java index 1e2897f0ed0..d5105c2bc89 100644 --- a/src/main/java/io/vertx/core/http/impl/WebSocketInternal.java +++ b/src/main/java/io/vertx/core/http/impl/WebSocketInternal.java @@ -12,9 +12,10 @@ import io.netty.channel.ChannelHandlerContext; import io.vertx.core.http.HttpConnection; +import io.vertx.core.http.WebSocket; import io.vertx.core.http.WebSocketBase; -public interface WebSocketInternal extends WebSocketBase { +public interface WebSocketInternal extends WebSocket { ChannelHandlerContext channelHandlerContext(); diff --git a/src/main/java/io/vertx/core/http/impl/cgbystrom/FlashPolicyHandler.java b/src/main/java/io/vertx/core/http/impl/cgbystrom/FlashPolicyHandler.java deleted file mode 100644 index 4252501253f..00000000000 --- a/src/main/java/io/vertx/core/http/impl/cgbystrom/FlashPolicyHandler.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.http.impl.cgbystrom; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.util.CharsetUtil; - -/** - * A Flash policy file handler - * Will detect connection attempts made by Adobe Flash clients and return a policy file response - * - * After the policy has been sent, it will instantly close the connection. - * If the first bytes sent are not a policy file request the handler will simply remove itself - * from the pipeline. - * - * Read more at http://www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html - * - * Example usage: - * - * ChannelPipeline pipeline = Channels.pipeline(); - * pipeline.addLast("flashPolicy", new FlashPolicyHandler()); - * pipeline.addLast("decoder", new MyProtocolDecoder()); - * pipeline.addLast("encoder", new MyProtocolEncoder()); - * pipeline.addLast("handler", new MyBusinessLogicHandler()); - * - * - * For license see LICENSE file in this directory - */ -public class FlashPolicyHandler extends ChannelInboundHandlerAdapter { - private static final String XML = ""; - - enum ParseState { - MAGIC1, - MAGIC2 - } - - private ParseState state = ParseState.MAGIC1; - - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - ByteBuf buffer = (ByteBuf) msg; - int index = buffer.readerIndex(); - switch (state) { - case MAGIC1: - if (!buffer.isReadable()) { - return; - } - final int magic1 = buffer.getUnsignedByte(index++); - state = ParseState.MAGIC2; - if (magic1 != '<') { - ctx.fireChannelRead(buffer); - ctx.pipeline().remove(this); - return; - } - // fall through - case MAGIC2: - if (!buffer.isReadable()) { - return; - } - final int magic2 = buffer.getUnsignedByte(index); - if (magic2 != 'p') { - ctx.fireChannelRead(buffer); - ctx.pipeline().remove(this); - } else { - ctx.writeAndFlush(Unpooled.copiedBuffer(XML, CharsetUtil.UTF_8)).addListener(ChannelFutureListener.CLOSE); - } - } - } -} diff --git a/src/main/java/io/vertx/core/http/impl/cgbystrom/LICENSE b/src/main/java/io/vertx/core/http/impl/cgbystrom/LICENSE deleted file mode 100644 index 7774cb0d8f2..00000000000 --- a/src/main/java/io/vertx/core/http/impl/cgbystrom/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License - -Copyright (c) 2009 Carl Bystrm - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/src/main/java/io/vertx/core/http/impl/ws/WebSocketFrameImpl.java b/src/main/java/io/vertx/core/http/impl/ws/WebSocketFrameImpl.java index bf58f275a3e..799f914eff6 100644 --- a/src/main/java/io/vertx/core/http/impl/ws/WebSocketFrameImpl.java +++ b/src/main/java/io/vertx/core/http/impl/ws/WebSocketFrameImpl.java @@ -16,6 +16,7 @@ import io.netty.util.CharsetUtil; import io.netty.util.ReferenceCounted; import io.vertx.core.buffer.Buffer; +import io.vertx.core.buffer.impl.BufferInternal; import io.vertx.core.http.WebSocketFrame; import io.vertx.core.http.WebSocketFrameType; @@ -31,7 +32,7 @@ public class WebSocketFrameImpl implements WebSocketFrameInternal, ReferenceCounted { public static WebSocketFrame binaryFrame(Buffer data, boolean isFinal) { - return new WebSocketFrameImpl(WebSocketFrameType.BINARY, data.getByteBuf(), isFinal); + return new WebSocketFrameImpl(WebSocketFrameType.BINARY, ((BufferInternal)data).getByteBuf(), isFinal); } public static WebSocketFrame textFrame(String str, boolean isFinal) { @@ -39,15 +40,15 @@ public static WebSocketFrame textFrame(String str, boolean isFinal) { } public static WebSocketFrame continuationFrame(Buffer data, boolean isFinal) { - return new WebSocketFrameImpl(WebSocketFrameType.CONTINUATION, data.getByteBuf(), isFinal); + return new WebSocketFrameImpl(WebSocketFrameType.CONTINUATION, ((BufferInternal)data).getByteBuf(), isFinal); } public static WebSocketFrame pingFrame(Buffer data) { - return new WebSocketFrameImpl(WebSocketFrameType.PING, data.getByteBuf(), true); + return new WebSocketFrameImpl(WebSocketFrameType.PING, ((BufferInternal)data).getByteBuf(), true); } public static WebSocketFrame pongFrame(Buffer data) { - return new WebSocketFrameImpl(WebSocketFrameType.PONG, data.getByteBuf(), true); + return new WebSocketFrameImpl(WebSocketFrameType.PONG, ((BufferInternal)data).getByteBuf(), true); } private final WebSocketFrameType type; @@ -145,7 +146,7 @@ public String textData() { } public Buffer binaryData() { - return Buffer.buffer(binaryData); + return BufferInternal.buffer(binaryData); } public void setBinaryData(ByteBuf binaryData) { diff --git a/src/main/java/io/vertx/core/impl/ConcurrentHashSet.java b/src/main/java/io/vertx/core/impl/ConcurrentHashSet.java deleted file mode 100644 index d6c6c67b2c8..00000000000 --- a/src/main/java/io/vertx/core/impl/ConcurrentHashSet.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl; - -import java.util.Collection; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -/** - * @author Tim Fox - */ -public class ConcurrentHashSet implements Set { - - private final Map map; - - private static final Object OBJ = new Object(); - - public ConcurrentHashSet(int size) { - map = new ConcurrentHashMap<>(size); - } - - public ConcurrentHashSet() { - map = new ConcurrentHashMap<>(); - } - - @Override - public int size() { - return map.size(); - } - - @Override - public boolean isEmpty() { - return map.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return map.containsKey(o); - } - - @Override - public Iterator iterator() { - return map.keySet().iterator(); - } - - @Override - public Object[] toArray() { - return map.keySet().toArray(); - } - - @Override - public T[] toArray(T[] a) { - return map.keySet().toArray(a); - } - - @Override - public boolean add(E e) { - return map.put(e, OBJ) == null; - } - - @Override - public boolean remove(Object o) { - return map.remove(o) != null; - } - - @Override - public boolean containsAll(Collection c) { - return map.keySet().containsAll(c); - } - - @Override - public boolean addAll(Collection c) { - boolean changed = false; - for (E e: c) { - if (map.put(e, OBJ) == null) { - changed = true; - } - } - return changed; - } - - @Override - public boolean retainAll(Collection c) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean removeAll(Collection c) { - throw new UnsupportedOperationException(); - } - - @Override - public void clear() { - map.clear(); - } -} diff --git a/src/main/java/io/vertx/core/impl/ContextBase.java b/src/main/java/io/vertx/core/impl/ContextImpl.java similarity index 62% rename from src/main/java/io/vertx/core/impl/ContextBase.java rename to src/main/java/io/vertx/core/impl/ContextImpl.java index ba34949eaa5..07f3733526b 100644 --- a/src/main/java/io/vertx/core/impl/ContextBase.java +++ b/src/main/java/io/vertx/core/impl/ContextImpl.java @@ -13,16 +13,14 @@ import io.netty.channel.EventLoop; import io.vertx.core.*; +import io.vertx.core.Future; import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; import io.vertx.core.json.JsonObject; import io.vertx.core.spi.metrics.PoolMetrics; import io.vertx.core.spi.tracing.VertxTracer; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Executor; -import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.*; /** * A base class for {@link Context} implementations. @@ -30,19 +28,21 @@ * @author Tim Fox * @author Julien Viet */ -public abstract class ContextBase implements ContextInternal { +public final class ContextImpl implements ContextInternal { - private static final Logger log = LoggerFactory.getLogger(ContextBase.class); + private static final Logger log = LoggerFactory.getLogger(ContextImpl.class); private static final String DISABLE_TIMINGS_PROP_NAME = "vertx.disableContextTimings"; static final boolean DISABLE_TIMINGS = Boolean.getBoolean(DISABLE_TIMINGS_PROP_NAME); + private final ThreadingModel threadingModel; private final VertxInternal owner; private final JsonObject config; private final Deployment deployment; private final CloseFuture closeFuture; private final ClassLoader tccl; private final EventLoop eventLoop; + private final EventExecutor executor; private ConcurrentMap data; private ConcurrentMap localData; private volatile Handler exceptionHandler; @@ -51,22 +51,27 @@ public abstract class ContextBase implements ContextInternal { final WorkerPool workerPool; final TaskQueue orderedTasks; - protected ContextBase(VertxInternal vertx, + protected ContextImpl(VertxInternal vertx, + ThreadingModel threadingModel, EventLoop eventLoop, + EventExecutor executor, WorkerPool internalWorkerPool, WorkerPool workerPool, + TaskQueue orderedTasks, Deployment deployment, CloseFuture closeFuture, ClassLoader tccl) { + this.threadingModel = threadingModel; this.deployment = deployment; this.config = deployment != null ? deployment.config() : new JsonObject(); this.eventLoop = eventLoop; + this.executor = executor; this.tccl = tccl; this.owner = vertx; this.workerPool = workerPool; this.closeFuture = closeFuture; this.internalWorkerPool = internalWorkerPool; - this.orderedTasks = new TaskQueue(); + this.orderedTasks = orderedTasks; this.internalOrderedTasks = new TaskQueue(); } @@ -93,26 +98,64 @@ public VertxInternal owner() { } @Override - public Future executeBlockingInternal(Handler> action) { + public Future executeBlockingInternal(Callable action) { return executeBlocking(this, action, internalWorkerPool, internalOrderedTasks); } @Override - public Future executeBlockingInternal(Handler> action, boolean ordered) { + public Future executeBlockingInternal(Callable action, boolean ordered) { return executeBlocking(this, action, internalWorkerPool, ordered ? internalOrderedTasks : null); } @Override - public Future executeBlocking(Handler> blockingCodeHandler, boolean ordered) { + public Future executeBlocking(Callable blockingCodeHandler, boolean ordered) { return executeBlocking(this, blockingCodeHandler, workerPool, ordered ? orderedTasks : null); } @Override - public Future executeBlocking(Handler> blockingCodeHandler, TaskQueue queue) { + public EventExecutor executor() { + return executor; + } + + @Override + public boolean isEventLoopContext() { + return threadingModel == ThreadingModel.EVENT_LOOP; + } + + @Override + public boolean isWorkerContext() { + return threadingModel == ThreadingModel.WORKER; + } + + public ThreadingModel threadingModel() { + return threadingModel; + } + + @Override + public boolean inThread() { + return executor.inThread(); + } + + @Override + public Future executeBlocking(Callable blockingCodeHandler, TaskQueue queue) { return executeBlocking(this, blockingCodeHandler, workerPool, queue); } - static Future executeBlocking(ContextInternal context, Handler> blockingCodeHandler, + static Future executeBlocking(ContextInternal context, Callable blockingCodeHandler, + WorkerPool workerPool, TaskQueue queue) { + return internalExecuteBlocking(context, promise -> { + T result; + try { + result = blockingCodeHandler.call(); + } catch (Throwable e) { + promise.fail(e); + return; + } + promise.complete(result); + }, workerPool, queue); + } + + private static Future internalExecuteBlocking(ContextInternal context, Handler> blockingCodeHandler, WorkerPool workerPool, TaskQueue queue) { PoolMetrics metrics = workerPool.metrics(); Object queueMetric = metrics != null ? metrics.submitted() : null; @@ -124,13 +167,7 @@ static Future executeBlocking(ContextInternal context, Handler if (metrics != null) { execMetric = metrics.begin(queueMetric); } - context.dispatch(promise, f -> { - try { - blockingCodeHandler.handle(promise); - } catch (Throwable e) { - promise.tryFail(e); - } - }); + context.dispatch(promise, blockingCodeHandler); if (metrics != null) { metrics.end(execMetric, fut.succeeded()); } @@ -205,33 +242,67 @@ public Handler exceptionHandler() { return exceptionHandler; } - @Override - public final void runOnContext(Handler action) { - runOnContext(this, action); + protected void runOnContext(ContextInternal ctx, Handler action) { + try { + Executor exec = ctx.executor(); + exec.execute(() -> ctx.dispatch(action)); + } catch (RejectedExecutionException ignore) { + // Pool is already shut down + } } - protected abstract void runOnContext(ContextInternal ctx, Handler action); - @Override public void execute(Runnable task) { execute(this, task); } - protected abstract void execute(ContextInternal ctx, Runnable task); - @Override public final void execute(T argument, Handler task) { execute(this, argument, task); } - protected abstract void execute(ContextInternal ctx, T argument, Handler task); + protected void execute(ContextInternal ctx, Runnable task) { + if (inThread()) { + task.run(); + } else { + executor.execute(task); + } + } + + /** + *
    + *
  • When the current thread is event-loop thread of this context the implementation will execute the {@code task} directly
  • + *
  • When the current thread is a worker thread of this context the implementation will execute the {@code task} directly
  • + *
  • Otherwise the task will be scheduled on the context thread for execution
  • + *
+ */ + protected void execute(ContextInternal ctx, T argument, Handler task) { + if (inThread()) { + task.handle(argument); + } else { + executor.execute(() -> task.handle(argument)); + } + } @Override public void emit(T argument, Handler task) { emit(this, argument, task); } - protected abstract void emit(ContextInternal ctx, T argument, Handler task); + protected void emit(ContextInternal ctx, T argument, Handler task) { + if (inThread()) { + ContextInternal prev = ctx.beginDispatch(); + try { + task.handle(argument); + } catch (Throwable t) { + reportException(t); + } finally { + ctx.endDispatch(prev); + } + } else { + executor.execute(() -> emit(ctx, argument, task)); + } + } @Override public ContextInternal duplicate() { diff --git a/src/main/java/io/vertx/core/impl/ContextInternal.java b/src/main/java/io/vertx/core/impl/ContextInternal.java index f04dfa8d18b..bd0248c3c3e 100644 --- a/src/main/java/io/vertx/core/impl/ContextInternal.java +++ b/src/main/java/io/vertx/core/impl/ContextInternal.java @@ -19,6 +19,7 @@ import io.vertx.core.impl.future.SucceededFuture; import io.vertx.core.spi.tracing.VertxTracer; +import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @@ -118,20 +119,20 @@ default Future failedFuture(String message) { } /** - * Like {@link #executeBlocking(Handler, boolean)} but uses the {@code queue} to order the tasks instead + * Like {@link #executeBlocking(Callable, boolean)} but uses the {@code queue} to order the tasks instead * of the internal queue of this context. */ - Future executeBlocking(Handler> blockingCodeHandler, TaskQueue queue); + Future executeBlocking(Callable blockingCodeHandler, TaskQueue queue); /** * Execute an internal task on the internal blocking ordered executor. */ - Future executeBlockingInternal(Handler> action); + Future executeBlockingInternal(Callable action); /** * Execute an internal task on the internal blocking ordered executor. */ - Future executeBlockingInternal(Handler> action, boolean ordered); + Future executeBlockingInternal(Callable action, boolean ordered); /** * @return the deployment associated with this context or {@code null} diff --git a/src/main/java/io/vertx/core/impl/DeploymentManager.java b/src/main/java/io/vertx/core/impl/DeploymentManager.java index bac58e8b236..0a2dffa5006 100644 --- a/src/main/java/io/vertx/core/impl/DeploymentManager.java +++ b/src/main/java/io/vertx/core/impl/DeploymentManager.java @@ -11,14 +11,7 @@ package io.vertx.core.impl; -import io.vertx.core.AsyncResult; -import io.vertx.core.CompositeFuture; -import io.vertx.core.Context; -import io.vertx.core.DeploymentOptions; -import io.vertx.core.Future; -import io.vertx.core.Handler; -import io.vertx.core.Promise; -import io.vertx.core.Verticle; +import io.vertx.core.*; import io.vertx.core.json.JsonObject; import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; @@ -45,10 +38,10 @@ public class DeploymentManager { private static final Logger log = LoggerFactory.getLogger(DeploymentManager.class); - private final VertxInternal vertx; + private final VertxImpl vertx; private final Map deployments = new ConcurrentHashMap<>(); - public DeploymentManager(VertxInternal vertx) { + public DeploymentManager(VertxImpl vertx) { this.vertx = vertx; } @@ -75,9 +68,9 @@ public Future deployVerticle(Callable verticleSupplier, Deploy public Future undeployVerticle(String deploymentID) { Deployment deployment = deployments.get(deploymentID); - Context currentContext = vertx.getOrCreateContext(); + ContextInternal currentContext = vertx.getOrCreateContext(); if (deployment == null) { - return ((ContextInternal) currentContext).failedFuture(new IllegalStateException("Unknown deployment")); + return currentContext.failedFuture(new IllegalStateException("Unknown deployment")); } else { return deployment.doUndeploy(vertx.getOrCreateContext()); } @@ -122,25 +115,6 @@ public Future undeployAll() { } } - private void reportFailure(Throwable t, ContextInternal context, Promise completionHandler) { - if (completionHandler != null) { - reportResult(context, completionHandler, Future.failedFuture(t)); - } else { - log.error(t.getMessage(), t); - } - } - - private void reportResult(Context context, Promise completionHandler, AsyncResult result) { - context.runOnContext(v -> { - try { - completionHandler.handle(result); - } catch (Throwable t) { - log.error("Failure in calling handler", t); - throw t; - } - }); - } - Future doDeploy(DeploymentOptions options, Function identifierProvider, ContextInternal parentContext, @@ -173,20 +147,41 @@ private Future doDeploy(String identifier, ContextInternal callingContext, ClassLoader tccl, Verticle... verticles) { Promise promise = callingContext.promise(); - String poolName = options.getWorkerPoolName(); - Deployment parent = parentContext.getDeployment(); String deploymentID = generateDeploymentID(); - DeploymentImpl deployment = new DeploymentImpl(parent, deploymentID, identifier, options); AtomicInteger deployCount = new AtomicInteger(); AtomicBoolean failureReported = new AtomicBoolean(); + WorkerPool workerPool = null; + ThreadingModel mode = options.getThreadingModel(); + if (mode == null) { + mode = ThreadingModel.EVENT_LOOP; + } + if (mode != ThreadingModel.VIRTUAL_THREAD) { + if (options.getWorkerPoolName() != null) { + workerPool = vertx.createSharedWorkerPool(options.getWorkerPoolName(), options.getWorkerPoolSize(), options.getMaxWorkerExecuteTime(), options.getMaxWorkerExecuteTimeUnit()); + } + } else { + if (!VertxInternal.isVirtualThreadAvailable()) { + return callingContext.failedFuture("This Java runtime does not support virtual threads"); + } + } + DeploymentImpl deployment = new DeploymentImpl(parent, workerPool, deploymentID, identifier, options); for (Verticle verticle: verticles) { CloseFuture closeFuture = new CloseFuture(log); - WorkerPool workerPool = poolName != null ? vertx.createSharedWorkerPool(poolName, options.getWorkerPoolSize(), options.getMaxWorkerExecuteTime(), options.getMaxWorkerExecuteTimeUnit()) : null; - ContextBase context = (options.isWorker() ? vertx.createWorkerContext(deployment, closeFuture, workerPool, tccl) : - vertx.createEventLoopContext(deployment, closeFuture, workerPool, tccl)); - VerticleHolder holder = new VerticleHolder(verticle, context, workerPool, closeFuture); + ContextImpl context; + switch (mode) { + default: + context = vertx.createEventLoopContext(deployment, closeFuture, workerPool, tccl); + break; + case WORKER: + context = vertx.createWorkerContext(deployment, closeFuture, workerPool, tccl); + break; + case VIRTUAL_THREAD: + context = vertx.createVirtualThreadContext(deployment, closeFuture, tccl); + break; + } + VerticleHolder holder = new VerticleHolder(verticle, context, closeFuture); deployment.addVerticle(holder); context.runOnContext(v -> { try { @@ -226,23 +221,17 @@ private Future doDeploy(String identifier, static class VerticleHolder { final Verticle verticle; - final ContextBase context; - final WorkerPool workerPool; + final ContextImpl context; final CloseFuture closeFuture; - VerticleHolder(Verticle verticle, ContextBase context, WorkerPool workerPool, CloseFuture closeFuture) { + VerticleHolder(Verticle verticle, ContextImpl context, CloseFuture closeFuture) { this.verticle = verticle; this.context = context; - this.workerPool = workerPool; this.closeFuture = closeFuture; } Future close() { - return closeFuture.close().andThen(ar -> { - if (workerPool != null) { - workerPool.close(); - } - }); + return closeFuture.close(); } } @@ -255,28 +244,33 @@ private class DeploymentImpl implements Deployment { private final JsonObject conf; private final String verticleIdentifier; private final List verticles = new CopyOnWriteArrayList<>(); - private final Set children = new ConcurrentHashSet<>(); + private final Set children = ConcurrentHashMap.newKeySet(); + private final WorkerPool workerPool; private final DeploymentOptions options; private Handler undeployHandler; private int status = ST_DEPLOYED; private volatile boolean child; - private DeploymentImpl(Deployment parent, String deploymentID, String verticleIdentifier, DeploymentOptions options) { + private DeploymentImpl(Deployment parent, WorkerPool workerPool, String deploymentID, String verticleIdentifier, DeploymentOptions options) { this.parent = parent; this.deploymentID = deploymentID; this.conf = options.getConfig() != null ? options.getConfig().copy() : new JsonObject(); this.verticleIdentifier = verticleIdentifier; this.options = options; + this.workerPool = workerPool; } public void addVerticle(VerticleHolder holder) { verticles.add(holder); } - private synchronized void rollback(ContextInternal callingContext, Promise completionPromise, ContextBase context, VerticleHolder closeFuture, Throwable cause) { + private synchronized void rollback(ContextInternal callingContext, Promise completionPromise, ContextImpl context, VerticleHolder closeFuture, Throwable cause) { if (status == ST_DEPLOYED) { status = ST_UNDEPLOYING; doUndeployChildren(callingContext).onComplete(childrenResult -> { + if (workerPool != null) { + workerPool.close(); + } Handler handler; synchronized (DeploymentImpl.this) { status = ST_UNDEPLOYED; @@ -330,14 +324,14 @@ public synchronized Future doUndeploy(ContextInternal undeployingContext) parent.removeChild(this); } for (VerticleHolder verticleHolder: verticles) { - ContextBase context = verticleHolder.context; + ContextImpl context = verticleHolder.context; Promise p = Promise.promise(); undeployFutures.add(p.future()); context.runOnContext(v -> { Promise stopPromise = undeployingContext.promise(); Future stopFuture = stopPromise.future(); stopFuture - .eventually(v2 -> { + .eventually(() -> { deployments.remove(deploymentID); return verticleHolder .close() @@ -355,6 +349,9 @@ public synchronized Future doUndeploy(ContextInternal undeployingContext) Promise resolvingPromise = undeployingContext.promise(); Future.all(undeployFutures).mapEmpty().onComplete(resolvingPromise); Future fut = resolvingPromise.future(); + if (workerPool != null) { + fut = fut.andThen(ar -> workerPool.close()); + } Handler handler = undeployHandler; if (handler != null) { undeployHandler = null; diff --git a/src/main/java/io/vertx/core/impl/DuplicatedContext.java b/src/main/java/io/vertx/core/impl/DuplicatedContext.java index 9484eb3d46f..d08cda4990a 100644 --- a/src/main/java/io/vertx/core/impl/DuplicatedContext.java +++ b/src/main/java/io/vertx/core/impl/DuplicatedContext.java @@ -14,10 +14,11 @@ import io.vertx.core.Context; import io.vertx.core.Future; import io.vertx.core.Handler; -import io.vertx.core.Promise; +import io.vertx.core.ThreadingModel; import io.vertx.core.json.JsonObject; import io.vertx.core.spi.tracing.VertxTracer; +import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; @@ -35,13 +36,18 @@ */ class DuplicatedContext implements ContextInternal { - protected final ContextBase delegate; + protected final ContextImpl delegate; private ConcurrentMap localData; - DuplicatedContext(ContextBase delegate) { + DuplicatedContext(ContextImpl delegate) { this.delegate = delegate; } + @Override + public ThreadingModel threadingModel() { + return delegate.threadingModel(); + } + @Override public boolean inThread() { return delegate.inThread(); @@ -124,28 +130,23 @@ public final ConcurrentMap localContextData() { } @Override - public final Future executeBlockingInternal(Handler> action) { - return ContextBase.executeBlocking(this, action, delegate.internalWorkerPool, delegate.internalOrderedTasks); - } - - @Override - public final Future executeBlockingInternal(Handler> action, boolean ordered) { - return ContextBase.executeBlocking(this, action, delegate.internalWorkerPool, ordered ? delegate.internalOrderedTasks : null); + public Future executeBlockingInternal(Callable action) { + return ContextImpl.executeBlocking(this, action, delegate.internalWorkerPool, delegate.internalOrderedTasks); } @Override - public final Future executeBlocking(Handler> action, boolean ordered) { - return ContextBase.executeBlocking(this, action, delegate.workerPool, ordered ? delegate.orderedTasks : null); + public Future executeBlockingInternal(Callable action, boolean ordered) { + return ContextImpl.executeBlocking(this, action, delegate.internalWorkerPool, ordered ? delegate.internalOrderedTasks : null); } @Override - public final Future executeBlocking(Handler> blockingCodeHandler, TaskQueue queue) { - return ContextBase.executeBlocking(this, blockingCodeHandler, delegate.workerPool, queue); + public final Future executeBlocking(Callable blockingCodeHandler, boolean ordered) { + return ContextImpl.executeBlocking(this, blockingCodeHandler, delegate.workerPool, ordered ? delegate.orderedTasks : null); } @Override - public final void runOnContext(Handler action) { - delegate.runOnContext(this, action); + public final Future executeBlocking(Callable blockingCodeHandler, TaskQueue queue) { + return ContextImpl.executeBlocking(this, blockingCodeHandler, delegate.workerPool, queue); } @Override diff --git a/src/main/java/io/vertx/core/impl/LoaderManager.java b/src/main/java/io/vertx/core/impl/EventExecutor.java similarity index 55% rename from src/main/java/io/vertx/core/impl/LoaderManager.java rename to src/main/java/io/vertx/core/impl/EventExecutor.java index caaa617d0a9..fa0bbc561ff 100644 --- a/src/main/java/io/vertx/core/impl/LoaderManager.java +++ b/src/main/java/io/vertx/core/impl/EventExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -10,20 +10,16 @@ */ package io.vertx.core.impl; -import io.vertx.core.DeploymentOptions; +import java.util.concurrent.Executor; /** - * No-op implementation for Java 11 and above. + * The Vert.x context event executor. */ -class LoaderManager { +public interface EventExecutor extends Executor { /** - * @return {@code null} + * @return whether the current thread is one of the event executor thread */ - ClassLoaderHolder getClassLoader(DeploymentOptions options) { - return null; - } + boolean inThread(); - void release(ClassLoaderHolder holder) { - } } diff --git a/src/main/java/io/vertx/core/impl/EventLoopContext.java b/src/main/java/io/vertx/core/impl/EventLoopContext.java deleted file mode 100644 index 69d1d60714e..00000000000 --- a/src/main/java/io/vertx/core/impl/EventLoopContext.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl; - -import io.netty.channel.EventLoop; -import io.vertx.core.Handler; - -import java.util.concurrent.Executor; -import java.util.concurrent.RejectedExecutionException; - -/** - * @author Tim Fox - */ -public class EventLoopContext extends ContextBase { - - EventLoopContext(VertxInternal vertx, - EventLoop eventLoop, - WorkerPool internalBlockingPool, - WorkerPool workerPool, - Deployment deployment, - CloseFuture closeFuture, - ClassLoader tccl) { - super(vertx, eventLoop, internalBlockingPool, workerPool, deployment, closeFuture, tccl); - } - - @Override - public Executor executor() { - return nettyEventLoop(); - } - - @Override - protected void runOnContext(ContextInternal ctx, Handler action) { - try { - nettyEventLoop().execute(() -> ctx.dispatch(action)); - } catch (RejectedExecutionException ignore) { - // Pool is already shut down - } - } - - @Override - protected void emit(ContextInternal ctx, T argument, Handler task) { - EventLoop eventLoop = nettyEventLoop(); - if (eventLoop.inEventLoop()) { - ContextInternal prev = ctx.beginDispatch(); - try { - task.handle(argument); - } catch (Throwable t) { - reportException(t); - } finally { - ctx.endDispatch(prev); - } - } else { - eventLoop.execute(() -> emit(ctx, argument, task)); - } - } - - /** - *
    - *
  • When the current thread is event-loop thread of this context the implementation will execute the {@code task} directly
  • - *
  • Otherwise the task will be scheduled on the event-loop thread for execution
  • - *
- */ - @Override - protected void execute(ContextInternal ctx, T argument, Handler task) { - EventLoop eventLoop = nettyEventLoop(); - if (eventLoop.inEventLoop()) { - task.handle(argument); - } else { - eventLoop.execute(() -> task.handle(argument)); - } - } - - @Override - protected void execute(ContextInternal ctx, Runnable task) { - EventLoop eventLoop = nettyEventLoop(); - if (eventLoop.inEventLoop()) { - task.run(); - } else { - eventLoop.execute(task); - } - } - - @Override - public boolean isEventLoopContext() { - return true; - } - - @Override - public boolean isWorkerContext() { - return false; - } - - @Override - public boolean inThread() { - return nettyEventLoop().inEventLoop(); - } - -} diff --git a/src/main/java/io/vertx/core/impl/EventLoopExecutor.java b/src/main/java/io/vertx/core/impl/EventLoopExecutor.java new file mode 100644 index 00000000000..73302c1bc3e --- /dev/null +++ b/src/main/java/io/vertx/core/impl/EventLoopExecutor.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.impl; + +import io.netty.channel.EventLoop; + +/** + * Execute events on an event-loop. + * + * @author Julien Viet + */ +public class EventLoopExecutor implements EventExecutor { + + private final EventLoop eventLoop; + + public EventLoopExecutor(EventLoop eventLoop) { + this.eventLoop = eventLoop; + } + + @Override + public boolean inThread() { + return eventLoop.inEventLoop(); + } + + @Override + public void execute(Runnable command) { + eventLoop.execute(command); + } +} diff --git a/src/main/java/io/vertx/core/impl/HAManager.java b/src/main/java/io/vertx/core/impl/HAManager.java index 842b3f485af..fb608eb66ee 100644 --- a/src/main/java/io/vertx/core/impl/HAManager.java +++ b/src/main/java/io/vertx/core/impl/HAManager.java @@ -11,6 +11,7 @@ package io.vertx.core.impl; +import io.vertx.codegen.annotations.Nullable; import io.vertx.core.*; import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; @@ -263,16 +264,18 @@ public void failDuringFailover(boolean fail) { private void doDeployVerticle(final String verticleName, DeploymentOptions deploymentOptions, final Handler> doneHandler) { final Handler> wrappedHandler = ar1 -> { - vertx.executeBlocking(fut -> { - if (ar1.succeeded()) { + Future fut; + if (ar1.succeeded()) { + fut = vertx.executeBlocking(() -> { // Tell the other nodes of the cluster about the verticle for HA purposes String deploymentID = ar1.result(); addToHA(deploymentID, verticleName, deploymentOptions); - fut.complete(deploymentID); - } else { - fut.fail(ar1.cause()); - } - }, false).onComplete(ar2 -> { + return deploymentID; + }, false); + } else { + fut = (Future) ar1; + } + fut.onComplete(ar2 -> { if (doneHandler != null) { doneHandler.handle(ar2); } else if (ar2.failed()) { @@ -339,7 +342,7 @@ private synchronized void checkQuorumWhenAdded(final String nodeID, final long s checkQuorumTimerID = -1L; if (!stopped) { // This can block on a monitor so it needs to run as a worker - vertx.executeBlockingInternal(fut -> { + vertx.executeBlockingInternal(() -> { if (System.currentTimeMillis() - start > 10000) { log.warn("Timed out waiting for group information to appear"); } else { @@ -348,7 +351,7 @@ private synchronized void checkQuorumWhenAdded(final String nodeID, final long s checkQuorumWhenAdded(nodeID, start); }); } - fut.complete(); + return null; }); } }); diff --git a/src/main/java/io/vertx/core/impl/AddressResolver.java b/src/main/java/io/vertx/core/impl/HostnameResolver.java similarity index 65% rename from src/main/java/io/vertx/core/impl/AddressResolver.java rename to src/main/java/io/vertx/core/impl/HostnameResolver.java index b644b02eef9..f90b9d20295 100644 --- a/src/main/java/io/vertx/core/impl/AddressResolver.java +++ b/src/main/java/io/vertx/core/impl/HostnameResolver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -13,31 +13,36 @@ import io.netty.channel.EventLoop; import io.netty.resolver.AddressResolverGroup; -import io.vertx.core.AsyncResult; -import io.vertx.core.Future; -import io.vertx.core.Handler; -import io.vertx.core.Vertx; +import io.vertx.core.*; import io.vertx.core.dns.AddressResolverOptions; import io.vertx.core.impl.future.PromiseInternal; -import io.vertx.core.impl.launcher.commands.ExecUtils; import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; -import io.vertx.core.spi.resolver.ResolverProvider; +import io.vertx.core.net.Address; +import io.vertx.core.net.SocketAddress; +import io.vertx.core.spi.resolver.dns.AddressResolverProvider; +import io.vertx.core.spi.resolver.address.AddressResolver; import java.io.File; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.file.Files; +import java.util.ArrayList; import java.util.List; +import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static io.vertx.core.impl.Utils.isLinux; + /** + * Resolves host names, using DNS and /etc/hosts config based on {@link AddressResolverOptions} + * * @author Julien Viet */ -public class AddressResolver { +public class HostnameResolver implements io.vertx.core.net.AddressResolver { - private static final Logger log = LoggerFactory.getLogger(AddressResolver.class); + private static final Logger log = LoggerFactory.getLogger(HostnameResolver.class); private static Pattern resolvOption(String regex) { return Pattern.compile("^[ \\t\\f]*options[^\n]+" + regex + "(?=$|\\s)", Pattern.MULTILINE); @@ -51,7 +56,7 @@ private static Pattern resolvOption(String regex) { static { int ndots = 1; boolean rotate = false; - if (ExecUtils.isLinux()) { + if (isLinux()) { File f = new File("/etc/resolv.conf"); try { if (f.exists() && f.isFile()) { @@ -72,14 +77,19 @@ private static Pattern resolvOption(String regex) { private final Vertx vertx; private final AddressResolverGroup resolverGroup; - private final ResolverProvider provider; + private final AddressResolverProvider provider; - public AddressResolver(Vertx vertx, AddressResolverOptions options) { - this.provider = ResolverProvider.factory(vertx, options); + public HostnameResolver(Vertx vertx, AddressResolverOptions options) { + this.provider = AddressResolverProvider.factory(vertx, options); this.resolverGroup = provider.resolver(options); this.vertx = vertx; } + @Override + public AddressResolver resolver(Vertx vertx) { + return new Impl(); + } + public Future resolveHostname(String hostname) { ContextInternal context = (ContextInternal) vertx.getOrCreateContext(); io.netty.util.concurrent.Future fut = resolveHostname(context.nettyEventLoop(), hostname); @@ -131,4 +141,52 @@ public static boolean parseRotateOptionFromResolvConf(String s) { Matcher matcher = ROTATE_OPTIONS_PATTERN.matcher(s); return matcher.find(); } + + class Impl implements AddressResolver, B> { + @Override + public SocketAddress tryCast(Address address) { + return address instanceof SocketAddress ? (SocketAddress) address : null; + } + + @Override + public SocketAddress addressOfEndpoint(SocketAddress endpoint) { + return endpoint; + } + + @Override + public Future> resolve(Function factory, SocketAddress address) { + Promise> promise = Promise.promise(); + resolveHostnameAll(address.host(), ar -> { + if (ar.succeeded()) { + List endpoints = new ArrayList<>(); + for (InetSocketAddress addr : ar.result()) { + endpoints.add(factory.apply(SocketAddress.inetSocketAddress(address.port(), addr.getAddress().getHostAddress()))); + } + promise.complete(endpoints); + } else { + promise.fail(ar.cause()); + } + }); + return promise.future(); + } + + @Override + public List endpoints(List state) { + return state; + } + + @Override + public boolean isValid(List state) { + // NEED EXPIRATION + return true; + } + + @Override + public void dispose(List state) { + } + + @Override + public void close() { + } + } } diff --git a/src/main/java/io/vertx/core/impl/IsolatingClassLoader.java b/src/main/java/io/vertx/core/impl/IsolatingClassLoader.java deleted file mode 100644 index e8420cf0e5d..00000000000 --- a/src/main/java/io/vertx/core/impl/IsolatingClassLoader.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl; - -import java.io.IOException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Collections; -import java.util.Enumeration; -import java.util.List; - -/** - * - * @author Tim Fox - */ -public class IsolatingClassLoader extends URLClassLoader { - - private volatile boolean closed; - private List isolatedClasses; - - public IsolatingClassLoader(URL[] urls, ClassLoader parent, List isolatedClasses) { - super(urls, parent); - this.isolatedClasses = isolatedClasses; - } - - @Override - protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - synchronized (getClassLoadingLock(name)) { - Class c = findLoadedClass(name); - if (c == null) { - if (isIsolatedClass(name)) { - // We don't want to load Vert.x (or Vert.x dependency) classes from an isolating loader - if (isVertxOrSystemClass(name)) { - try { - c = getParent().loadClass(name); - } catch (ClassNotFoundException e) { - // Fall through - } - } - if (c == null) { - // Try and load with this classloader - try { - c = findClass(name); - } catch (ClassNotFoundException e) { - // Now try with parent - c = getParent().loadClass(name); - } - } - if (resolve) { - resolveClass(c); - } - } else { - // Parent first - c = super.loadClass(name, resolve); - } - } - return c; - } - } - - private boolean isIsolatedClass(String name) { - if (isolatedClasses != null) { - for (String isolated : isolatedClasses) { - if (isolated.endsWith(".*")) { - String isolatedPackage = isolated.substring(0, isolated.length() - 1); - String paramPackage = name.substring(0, name.lastIndexOf('.') + 1); - if (paramPackage.startsWith(isolatedPackage)) { - // Matching package - return true; - } - } else if (isolated.equals(name)) { - return true; - } - } - } - return false; - } - - - /** - * {@inheritDoc} - */ - @Override - public URL getResource(String name) { - - // First check this classloader - URL url = findResource(name); - - // Then try the parent if not found - if (url == null) { - url = super.getResource(name); - } - - return url; - } - - /** - * {@inheritDoc} - */ - @Override - public Enumeration getResources(String name) throws IOException { - - // First get resources from this classloader - List resources = Collections.list(findResources(name)); - - // Then add resources from the parent - if (getParent() != null) { - Enumeration parentResources = getParent().getResources(name); - if (parentResources.hasMoreElements()) { - resources.addAll(Collections.list(parentResources)); - } - } - - return Collections.enumeration(resources); - } - - @Override - public void close() throws IOException { - closed = true; - super.close(); - } - - public boolean isClosed() { - return closed; - } - - private boolean isVertxOrSystemClass(String name) { - return - name.startsWith("java.") || - name.startsWith("javax.") || - name.startsWith("sun.*") || - name.startsWith("com.sun.") || - name.startsWith("io.vertx.core") || - name.startsWith("io.netty.") || - name.startsWith("com.fasterxml.jackson"); - } -} diff --git a/src/main/java/io/vertx/core/impl/ClassLoaderHolder.java b/src/main/java/io/vertx/core/impl/NoStackTraceException.java similarity index 58% rename from src/main/java/io/vertx/core/impl/ClassLoaderHolder.java rename to src/main/java/io/vertx/core/impl/NoStackTraceException.java index 2566dfc5da4..0cded0be7d2 100644 --- a/src/main/java/io/vertx/core/impl/ClassLoaderHolder.java +++ b/src/main/java/io/vertx/core/impl/NoStackTraceException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -8,19 +8,21 @@ * * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ + package io.vertx.core.impl; +import io.vertx.core.VertxException; + /** * @author Julien Viet */ -class ClassLoaderHolder { +public class NoStackTraceException extends VertxException { - final String group; - final ClassLoader loader; - int refCount; + public NoStackTraceException(String message) { + super(message, null, true); + } - ClassLoaderHolder(String group, ClassLoader loader) { - this.group = group; - this.loader = loader; + public NoStackTraceException(Throwable cause) { + super(cause, true); } } diff --git a/src/main/java/io/vertx/core/http/impl/NoStackTraceTimeoutException.java b/src/main/java/io/vertx/core/impl/NoStackTraceTimeoutException.java similarity index 78% rename from src/main/java/io/vertx/core/http/impl/NoStackTraceTimeoutException.java rename to src/main/java/io/vertx/core/impl/NoStackTraceTimeoutException.java index ffa6285c626..fc106c39106 100644 --- a/src/main/java/io/vertx/core/http/impl/NoStackTraceTimeoutException.java +++ b/src/main/java/io/vertx/core/impl/NoStackTraceTimeoutException.java @@ -8,16 +8,19 @@ * * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ -package io.vertx.core.http.impl; +package io.vertx.core.impl; import java.util.concurrent.TimeoutException; -class NoStackTraceTimeoutException extends TimeoutException { - NoStackTraceTimeoutException(String message) { +public class NoStackTraceTimeoutException extends TimeoutException { + + public NoStackTraceTimeoutException(String message) { super(message); } + @Override public synchronized Throwable fillInStackTrace() { return this; } + } diff --git a/src/main/java/io/vertx/core/impl/TaskQueue.java b/src/main/java/io/vertx/core/impl/TaskQueue.java index 59190f06d78..fd4632d44c5 100644 --- a/src/main/java/io/vertx/core/impl/TaskQueue.java +++ b/src/main/java/io/vertx/core/impl/TaskQueue.java @@ -15,8 +15,10 @@ import io.vertx.core.impl.logging.LoggerFactory; import java.util.LinkedList; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; /** * A task queue that always run all tasks in order. The executor to run the tasks is passed @@ -34,22 +36,10 @@ public class TaskQueue { static final Logger log = LoggerFactory.getLogger(TaskQueue.class); - private static class Task { - - private final Runnable runnable; - private final Executor exec; - - public Task(Runnable runnable, Executor exec) { - this.runnable = runnable; - this.exec = exec; - } - } - // @protectedby tasks private final LinkedList tasks = new LinkedList<>(); - - // @protectedby tasks - private Executor current; + private Executor currentExecutor; + private Thread currentThread; private final Runnable runner; @@ -59,27 +49,92 @@ public TaskQueue() { private void run() { for (; ; ) { - final Task task; + final ExecuteTask execute; synchronized (tasks) { - task = tasks.poll(); + Task task = tasks.poll(); if (task == null) { - current = null; + currentExecutor = null; return; } - if (task.exec != current) { - tasks.addFirst(task); - task.exec.execute(runner); - current = task.exec; + if (task instanceof ResumeTask) { + ResumeTask resume = (ResumeTask) task; + currentExecutor = resume.executor; + currentThread = resume.thread; + resume.latch.run(); + return; + } + execute = (ExecuteTask) task; + if (execute.exec != currentExecutor) { + tasks.addFirst(execute); + execute.exec.execute(runner); + currentExecutor = execute.exec; return; } } try { - task.runnable.run(); + currentThread = Thread.currentThread(); + execute.runnable.run(); } catch (Throwable t) { log.error("Caught unexpected Throwable", t); + } finally { + currentThread = null; } } - }; + } + + /** + * Return a controller for the current task. + * + * @return the controller + * @throws IllegalStateException if the current thread is not currently being executed by the queue + */ + public WorkerExecutor.TaskController current() { + Thread thread; + Executor executor; + synchronized (tasks) { + if (Thread.currentThread() != currentThread) { + throw new IllegalStateException(); + } + thread = currentThread; + executor = currentExecutor; + } + return new WorkerExecutor.TaskController() { + + final CountDownLatch latch = new CountDownLatch(1); + + @Override + public void resume(Runnable callback) { + Runnable task = () -> { + callback.run(); + latch.countDown(); + }; + synchronized (tasks) { + if (currentExecutor != null) { + tasks.addFirst(new ResumeTask(task, executor, thread)); + return; + } + currentExecutor = executor; + currentThread = thread; + } + task.run(); + } + + @Override + public CountDownLatch suspend() { + if (Thread.currentThread() != thread) { + throw new IllegalStateException(); + } + synchronized (tasks) { + if (currentThread == null || currentThread != Thread.currentThread()) { + throw new IllegalStateException(); + } + currentThread = null; + } + executor.execute(runner); + return latch; + } + }; + } /** * Run a task. @@ -88,16 +143,59 @@ private void run() { */ public void execute(Runnable task, Executor executor) { synchronized (tasks) { - tasks.add(new Task(task, executor)); - if (current == null) { - current = executor; + if (currentExecutor == null) { + currentExecutor = executor; try { executor.execute(runner); } catch (RejectedExecutionException e) { - current = null; + currentExecutor = null; throw e; } } + // Add the task after the runner has been accepted to the executor + // to cover the case of a rejected execution exception. + tasks.add(new ExecuteTask(task, executor)); + } + } + + /** + * Test if the task queue is empty and no current executor is running anymore. + */ + public boolean isEmpty() { + synchronized (tasks) { + return tasks.isEmpty() && currentExecutor == null; + } + } + + /** + * A task of this queue. + */ + private interface Task { + } + + /** + * Execute another task + */ + private static class ExecuteTask implements Task { + private final Runnable runnable; + private final Executor exec; + public ExecuteTask(Runnable runnable, Executor exec) { + this.runnable = runnable; + this.exec = exec; + } + } + + /** + * Resume an existing task blocked on a thread + */ + private static class ResumeTask implements Task { + private final Runnable latch; + private final Executor executor; + private final Thread thread; + ResumeTask(Runnable latch, Executor executor, Thread thread) { + this.latch = latch; + this.executor = executor; + this.thread = thread; } } } diff --git a/src/main/java/io/vertx/core/impl/ThreadPerTaskExecutorService.java b/src/main/java/io/vertx/core/impl/ThreadPerTaskExecutorService.java new file mode 100644 index 00000000000..178ffe08784 --- /dev/null +++ b/src/main/java/io/vertx/core/impl/ThreadPerTaskExecutorService.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.impl; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +public class ThreadPerTaskExecutorService extends AbstractExecutorService { + + private static final int ST_RUNNING = 0; + private static final int ST_SHUTTING_DOWN = 1; + private static final int ST_TERMINATED = 2; + + private final AtomicInteger state = new AtomicInteger(); + private final Set threads = ConcurrentHashMap.newKeySet(); + private final CountDownLatch terminated = new CountDownLatch(1); + private final ThreadFactory threadFactory; + + public ThreadPerTaskExecutorService(ThreadFactory threadFactory) { + this.threadFactory = Objects.requireNonNull(threadFactory); + } + + @Override + public void shutdown() { + shutdown(false); + } + + @Override + public List shutdownNow() { + shutdown(true); + return Collections.emptyList(); + } + + private void shutdown(boolean now) { + if (state.get() == ST_RUNNING && state.compareAndSet(ST_RUNNING, ST_SHUTTING_DOWN)) { + if (threads.isEmpty()) { + state.set(ST_TERMINATED); + terminated.countDown(); + } else { + if (now) { + for (Thread thread : threads) { + thread.interrupt(); + } + } + } + } + } + + @Override + public boolean isShutdown() { + return state.get() != ST_RUNNING; + } + + @Override + public boolean isTerminated() { + return state.get() == ST_TERMINATED; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return terminated.await(timeout, unit); + } + + @Override + public void execute(Runnable command) { + Objects.requireNonNull(command); + if (state.get() == ST_RUNNING) { + Thread thread = threadFactory.newThread(() -> { + try { + command.run(); + } finally { + threads.remove(Thread.currentThread()); + if (state.get() == ST_SHUTTING_DOWN && threads.isEmpty()) { + if (state.compareAndSet(ST_SHUTTING_DOWN, ST_TERMINATED)) { + terminated.countDown(); + } + } + } + }); + threads.add(thread); + thread.start(); + } else { + throw new RejectedExecutionException(); + } + } +} diff --git a/src/main/java/io/vertx/core/impl/TimerImpl.java b/src/main/java/io/vertx/core/impl/TimerImpl.java new file mode 100644 index 00000000000..b4c337d4d80 --- /dev/null +++ b/src/main/java/io/vertx/core/impl/TimerImpl.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.impl; + +import io.netty.util.concurrent.FutureListener; +import io.vertx.core.Timer; +import io.vertx.core.impl.future.FutureImpl; + +/** + * A timer task as a vertx future. + * + * @author Julien Viet + */ +class TimerImpl extends FutureImpl implements FutureListener, Timer { + + private final io.netty.util.concurrent.ScheduledFuture delegate; + + TimerImpl(ContextInternal ctx, io.netty.util.concurrent.ScheduledFuture delegate) { + super(ctx); + this.delegate = delegate; + } + + @Override + public boolean cancel() { + return delegate.cancel(false); + } + + @Override + public void operationComplete(io.netty.util.concurrent.Future future) { + if (future.isSuccess()) { + tryComplete(null); + } else { + tryFail(future.cause()); + } + } +} diff --git a/src/main/java/io/vertx/core/impl/Utils.java b/src/main/java/io/vertx/core/impl/Utils.java index 9aedf0d2716..9b604093d99 100644 --- a/src/main/java/io/vertx/core/impl/Utils.java +++ b/src/main/java/io/vertx/core/impl/Utils.java @@ -45,4 +45,8 @@ public static boolean isWindows() { return isWindows; } + @SuppressWarnings("unchecked") + public static void throwAsUnchecked(Throwable t) throws E { + throw (E) t; + } } diff --git a/src/main/java/io/vertx/core/impl/VerticleManager.java b/src/main/java/io/vertx/core/impl/VerticleManager.java index dc4adaaa9db..0a2477dc6db 100644 --- a/src/main/java/io/vertx/core/impl/VerticleManager.java +++ b/src/main/java/io/vertx/core/impl/VerticleManager.java @@ -34,7 +34,6 @@ public class VerticleManager { private final VertxInternal vertx; private final DeploymentManager deploymentManager; - private final LoaderManager loaderManager = new LoaderManager(); private final Map> verticleFactories = new ConcurrentHashMap<>(); private final List defaultFactories = new ArrayList<>(); @@ -145,29 +144,11 @@ private static String getSuffix(int pos, String str) { public Future deployVerticle(String identifier, DeploymentOptions options) { ContextInternal callingContext = vertx.getOrCreateContext(); - ClassLoaderHolder holder; ClassLoader loader = options.getClassLoader(); if (loader == null) { - holder = loaderManager.getClassLoader(options); - loader = holder != null ? holder.loader : getCurrentClassLoader(); - } else { - holder = null; - } - Future deployment = doDeployVerticle(identifier, options, callingContext, callingContext, loader); - if (holder != null) { - deployment.onComplete(ar -> { - if (ar.succeeded()) { - Deployment result = ar.result(); - result.undeployHandler(v -> { - loaderManager.release(holder); - }); - } else { - // ??? not tested - throw new UnsupportedOperationException(); - } - }); + loader = getCurrentClassLoader(); } - return deployment; + return doDeployVerticle(identifier, options, callingContext, callingContext, loader); } private Future doDeployVerticle(String identifier, diff --git a/src/main/java/io/vertx/core/impl/VertxBuilder.java b/src/main/java/io/vertx/core/impl/VertxBuilder.java index 59eac4d49d6..e83a2d99ac4 100644 --- a/src/main/java/io/vertx/core/impl/VertxBuilder.java +++ b/src/main/java/io/vertx/core/impl/VertxBuilder.java @@ -20,7 +20,6 @@ import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; import io.vertx.core.json.JsonObject; -import io.vertx.core.metrics.MetricsOptions; import io.vertx.core.spi.transport.Transport; import io.vertx.core.spi.ExecutorServiceFactory; import io.vertx.core.spi.VertxMetricsFactory; @@ -32,7 +31,6 @@ import io.vertx.core.spi.cluster.impl.DefaultNodeSelector; import io.vertx.core.spi.metrics.VertxMetrics; import io.vertx.core.spi.tracing.VertxTracer; -import io.vertx.core.tracing.TracingOptions; import java.util.ArrayList; import java.util.Collection; @@ -52,9 +50,11 @@ public class VertxBuilder { private Transport transport; private ClusterManager clusterManager; private NodeSelector clusterNodeSelector; + private VertxTracerFactory tracerFactory; private VertxTracer tracer; private VertxThreadFactory threadFactory; private ExecutorServiceFactory executorServiceFactory; + private VertxMetricsFactory metricsFactory; private VertxMetrics metrics; private FileResolver fileResolver; @@ -119,6 +119,11 @@ public VertxBuilder clusterManager(ClusterManager clusterManager) { return this; } + public VertxBuilder metricsFactory(VertxMetricsFactory factory) { + this.metricsFactory = factory; + return this; + } + /** * @return the node selector to use */ @@ -136,6 +141,11 @@ public VertxBuilder clusterNodeSelector(NodeSelector selector) { return this; } + public VertxBuilder tracerFactory(VertxTracerFactory factory) { + this.tracerFactory = factory; + return this; + } + /** * @return the tracer instance to use */ @@ -267,10 +277,10 @@ public Future clusteredVertx() { */ public VertxBuilder init() { initTransport(); + initMetrics(); + initTracing(); Collection providers = new ArrayList<>(); - initMetrics(options, providers); - initTracing(options, providers); - initClusterManager(options, providers); + initClusterManager(providers); providers.addAll(ServiceHelper.loadFactories(VertxServiceProvider.class)); initProviders(providers); initThreadFactory(); @@ -281,47 +291,41 @@ public VertxBuilder init() { private void initProviders(Collection providers) { for (VertxServiceProvider provider : providers) { + if (provider instanceof VertxMetricsFactory && (options.getMetricsOptions() == null || !options.getMetricsOptions().isEnabled())) { + continue; + } else if (provider instanceof VertxTracerFactory && (options.getTracingOptions() == null)) { + continue; + } provider.init(this); } } - private static void initMetrics(VertxOptions options, Collection providers) { - MetricsOptions metricsOptions = options.getMetricsOptions(); - if (metricsOptions != null) { - VertxMetricsFactory factory = metricsOptions.getFactory(); - if (factory != null) { - providers.add(factory); - } + private void initMetrics() { + VertxMetricsFactory provider = metricsFactory; + if (provider != null) { + provider.init(this); } } - private static void initTracing(VertxOptions options, Collection providers) { - TracingOptions tracingOptions = options.getTracingOptions(); - if (tracingOptions != null) { - VertxTracerFactory factory = tracingOptions.getFactory(); - if (factory != null) { - providers.add(factory); - } + private void initTracing() { + VertxTracerFactory provider = tracerFactory; + if (provider != null) { + provider.init(this); } } - private static void initClusterManager(VertxOptions options, Collection providers) { - ClusterManager clusterManager = options.getClusterManager(); - if (clusterManager == null) { - String clusterManagerClassName = System.getProperty("vertx.cluster.managerClass"); - if (clusterManagerClassName != null) { - // We allow specify a sys prop for the cluster manager factory which overrides ServiceLoader - try { - Class clazz = Class.forName(clusterManagerClassName); - clusterManager = (ClusterManager) clazz.getDeclaredConstructor().newInstance(); - } catch (Exception e) { - throw new IllegalStateException("Failed to instantiate " + clusterManagerClassName, e); - } + private static void initClusterManager(Collection providers) { + String clusterManagerClassName = System.getProperty("vertx.cluster.managerClass"); + if (clusterManagerClassName != null) { + // We allow specify a sys prop for the cluster manager factory which overrides ServiceLoader + try { + Class clazz = Class.forName(clusterManagerClassName); + ClusterManager clusterManager = (ClusterManager) clazz.getDeclaredConstructor().newInstance(); + providers.add(clusterManager); + } catch (Exception e) { + throw new IllegalStateException("Failed to instantiate " + clusterManagerClassName, e); } } - if (clusterManager != null) { - providers.add(clusterManager); - } } private void initTransport() { diff --git a/src/main/java/io/vertx/core/impl/VertxImpl.java b/src/main/java/io/vertx/core/impl/VertxImpl.java index 6e83fbbd896..a9d8115cbf7 100644 --- a/src/main/java/io/vertx/core/impl/VertxImpl.java +++ b/src/main/java/io/vertx/core/impl/VertxImpl.java @@ -18,6 +18,7 @@ import io.netty.util.concurrent.GenericFutureListener; import io.vertx.core.Future; import io.vertx.core.*; +import io.vertx.core.Timer; import io.vertx.core.datagram.DatagramSocket; import io.vertx.core.datagram.DatagramSocketOptions; import io.vertx.core.datagram.impl.DatagramSocketImpl; @@ -31,8 +32,7 @@ import io.vertx.core.eventbus.impl.clustered.ClusteredEventBus; import io.vertx.core.file.FileSystem; import io.vertx.core.http.*; -import io.vertx.core.http.impl.CleanableHttpClient; -import io.vertx.core.http.impl.HttpClientInternal; +import io.vertx.core.http.impl.*; import io.vertx.core.impl.btc.BlockedThreadChecker; import io.vertx.core.net.*; import io.vertx.core.net.impl.*; @@ -40,12 +40,10 @@ import io.vertx.core.spi.file.FileResolver; import io.vertx.core.file.impl.FileSystemImpl; import io.vertx.core.file.impl.WindowsFileSystem; -import io.vertx.core.http.impl.HttpClientImpl; -import io.vertx.core.http.impl.HttpServerImpl; import io.vertx.core.impl.future.PromiseInternal; import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; -import io.vertx.core.impl.resolver.DnsResolverProvider; +import io.vertx.core.dns.impl.DnsAddressResolverProvider; import io.vertx.core.spi.transport.Transport; import io.vertx.core.shareddata.SharedData; import io.vertx.core.shareddata.impl.SharedDataImpl; @@ -62,10 +60,13 @@ import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.lang.ref.Cleaner; import java.lang.ref.WeakReference; +import java.lang.reflect.Method; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; @@ -78,6 +79,8 @@ */ public class VertxImpl implements VertxInternal, MetricsProvider { + private static String version; + /** * Default shared cleaner for Vert.x */ @@ -96,6 +99,9 @@ public class VertxImpl implements VertxInternal, MetricsProvider { private static final String NETTY_IO_RATIO_PROPERTY_NAME = "vertx.nettyIORatio"; private static final int NETTY_IO_RATIO = Integer.getInteger(NETTY_IO_RATIO_PROPERTY_NAME, 50); + public static final ThreadFactory VIRTUAL_THREAD_FACTORY; + private static final Throwable VIRTUAL_THREAD_FACTORY_UNAVAILABILITY_CAUSE; + static { // Disable Netty's resource leak detection to reduce the performance overhead if not set by user // Supports both the default netty leak detection system property and the deprecated one @@ -103,6 +109,23 @@ public class VertxImpl implements VertxInternal, MetricsProvider { System.getProperty("io.netty.leakDetectionLevel") == null) { ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.DISABLED); } + + ThreadFactory factory = null; + Throwable unavailabilityCause = null; + try { + Class builderClass = ClassLoader.getSystemClassLoader().loadClass("java.lang.Thread$Builder"); + Class ofVirtualClass = ClassLoader.getSystemClassLoader().loadClass("java.lang.Thread$Builder$OfVirtual"); + Method ofVirtualMethod = Thread.class.getDeclaredMethod("ofVirtual"); + Object builder = ofVirtualMethod.invoke(null); + Method nameMethod = ofVirtualClass.getDeclaredMethod("name", String.class, long.class); + Method factoryMethod = builderClass.getDeclaredMethod("factory"); + builder = nameMethod.invoke(builder, "vert.x-virtual-thread-", 0L); + factory = (ThreadFactory) factoryMethod.invoke(builder); + } catch (Exception e) { + unavailabilityCause = e; + } + VIRTUAL_THREAD_FACTORY = factory; + VIRTUAL_THREAD_FACTORY_UNAVAILABILITY_CAUSE = unavailabilityCause; } private final FileSystem fileSystem = getFileSystem(); @@ -119,13 +142,15 @@ public class VertxImpl implements VertxInternal, MetricsProvider { private final Map sharedNetServers = new HashMap<>(); final WorkerPool workerPool; final WorkerPool internalWorkerPool; + final WorkerPool virtualThreaWorkerPool; private final VertxThreadFactory threadFactory; private final ExecutorServiceFactory executorServiceFactory; private final ThreadFactory eventLoopThreadFactory; private final EventLoopGroup eventLoopGroup; private final EventLoopGroup acceptorEventLoopGroup; + private final ExecutorService virtualThreadExecutor; private final BlockedThreadChecker checker; - private final AddressResolver addressResolver; + private final HostnameResolver hostnameResolver; private final AddressResolverOptions addressResolverOptions; private final EventBusInternal eventBus; private volatile HAManager haManager; @@ -176,6 +201,8 @@ public class VertxImpl implements VertxInternal, MetricsProvider { // The acceptor event loop thread needs to be from a different pool otherwise can get lags in accepted connections // under a lot of load acceptorEventLoopGroup = transport.eventLoopGroup(Transport.ACCEPTOR_EVENT_LOOP_GROUP, 1, acceptorEventLoopThreadFactory, 100); + virtualThreadExecutor = VIRTUAL_THREAD_FACTORY != null ? new ThreadPerTaskExecutorService(VIRTUAL_THREAD_FACTORY) : null; + virtualThreaWorkerPool = new WorkerPool(virtualThreadExecutor, null); internalWorkerPool = new WorkerPool(internalWorkerExec, internalBlockingPoolMetrics); workerPool = new WorkerPool(workerExec, workerPoolMetrics); defaultWorkerPoolSize = options.getWorkerPoolSize(); @@ -190,7 +217,7 @@ public class VertxImpl implements VertxInternal, MetricsProvider { this.transport = transport; this.fileResolver = fileResolver; this.addressResolverOptions = options.getAddressResolverOptions(); - this.addressResolver = new AddressResolver(this, options.getAddressResolverOptions()); + this.hostnameResolver = new HostnameResolver(this, options.getAddressResolverOptions()); this.tracer = tracer == VertxTracer.NOOP ? null : tracer; this.clusterManager = clusterManager; this.nodeSelector = nodeSelector; @@ -237,9 +264,9 @@ Future initClustered(VertxOptions options) { private void createHaManager(VertxOptions options, Promise initPromise) { if (options.isHAEnabled()) { - this.executeBlocking(fut -> { + this.executeBlocking(() -> { haManager = new HAManager(this, deploymentManager, verticleManager, clusterManager, clusterManager.getSyncMap(CLUSTER_MAP_NAME), options.getQuorumSize(), options.getHAGroup()); - fut.complete(haManager); + return haManager; }, false).onComplete(ar -> { if (ar.succeeded()) { startEventBus(true, initPromise); @@ -269,12 +296,12 @@ private void startEventBus(boolean haEnabled, Promise initPromise) { } private void initializeHaManager(Promise initPromise) { - this.executeBlocking(fut -> { + this.executeBlocking(() -> { // Init the manager (i.e register listener and check the quorum) // after the event bus has been fully started and updated its state // it will have also set the clustered changed view handler on the ha manager haManager.init(); - fut.complete(); + return null; }, false).onComplete(initPromise); } @@ -323,6 +350,11 @@ public Transport transport() { return transport; } + @Override + public Cleaner cleaner() { + return cleaner; + } + @Override public boolean isNativeTransportEnabled() { return !(transport instanceof JDKTransport); @@ -348,28 +380,49 @@ public HttpServer createHttpServer(HttpServerOptions serverOptions) { return new HttpServerImpl(this, serverOptions); } - public HttpClient createHttpClient(HttpClientOptions options) { - CloseFuture cf = resolveCloseFuture(); - HttpClient client; + @Override + public WebSocketClient createWebSocketClient(WebSocketClientOptions options) { + HttpClientOptions o = new HttpClientOptions(options); + o.setDefaultHost(options.getDefaultHost()); + o.setDefaultPort(options.getDefaultPort()); + o.setVerifyHost(options.isVerifyHost()); + o.setShared(options.isShared()); + o.setName(options.getName()); CloseFuture cf = resolveCloseFuture(); + WebSocketClient client; Closeable closeable; if (options.isShared()) { CloseFuture closeFuture = new CloseFuture(); - client = createSharedResource("__vertx.shared.httpClients", options.getName(), closeFuture, cf_ -> { - HttpClientImpl impl = new HttpClientImpl(this, options); + client = createSharedResource("__vertx.shared.webSocketClients", options.getName(), closeFuture, cf_ -> { + WebSocketClientImpl impl = new WebSocketClientImpl(this, o, options); cf_.add(completion -> impl.close().onComplete(completion)); return impl; }); - client = new CleanableHttpClient((HttpClientInternal) client, cleaner, (timeout, timeunit) -> closeFuture.close()); + client = new CleanableWebSocketClient(client, cleaner, (timeout, timeunit) -> closeFuture.close()); closeable = closeFuture; } else { - HttpClientImpl impl = new HttpClientImpl(this, options); + WebSocketClientImpl impl = new WebSocketClientImpl(this, o, options); closeable = impl; - client = new CleanableHttpClient(impl, cleaner, impl::close); + client = new CleanableWebSocketClient(impl, cleaner, impl::close); } cf.add(closeable); return client; } + @Override + public HttpClient createHttpClient(HttpClientOptions options) { + return createHttpClient(options, new PoolOptions()); + } + + @Override + public HttpClient createHttpClient(PoolOptions poolOptions) { + return createHttpClient(new HttpClientOptions(), poolOptions); + } + + @Override + public HttpClientBuilder httpClientBuilder() { + return new HttpClientBuilderInternal(this); + } + public EventBus eventBus() { return eventBus; } @@ -377,7 +430,7 @@ public EventBus eventBus() { @Override public long setPeriodic(long initialDelay, long delay, Handler handler) { ContextInternal ctx = getOrCreateContext(); - return scheduleTimeout(ctx, true, initialDelay, delay, TimeUnit.MILLISECONDS, ctx.isDeployment(), handler); + return scheduleTimeout(ctx.unwrap(), true, initialDelay, delay, TimeUnit.MILLISECONDS, ctx.isDeployment(), handler); } public long setTimer(long delay, Handler handler) { @@ -385,6 +438,19 @@ public long setTimer(long delay, Handler handler) { return scheduleTimeout(ctx, false, delay, TimeUnit.MILLISECONDS, ctx.isDeployment(), handler); } + @Override + public Timer timer(long delay, TimeUnit unit) { + Objects.requireNonNull(unit); + if (delay <= 0) { + throw new IllegalArgumentException("Invalid delay: " + delay); + } + ContextInternal ctx = getOrCreateContext(); + io.netty.util.concurrent.ScheduledFuture fut = ctx.nettyEventLoop().schedule(() -> null, delay, unit); + TimerImpl promise = new TimerImpl(ctx, fut); + fut.addListener(promise); + return promise; + } + @Override public PromiseInternal promise() { ContextInternal context = getOrCreateContext(); @@ -474,31 +540,69 @@ public boolean cancelTimer(long id) { } } + private ContextImpl createEventLoopContext(EventLoop eventLoop, CloseFuture closeFuture, WorkerPool workerPool, Deployment deployment, ClassLoader tccl) { + return new ContextImpl(this, ThreadingModel.EVENT_LOOP, eventLoop, new EventLoopExecutor(eventLoop), internalWorkerPool, workerPool != null ? workerPool : this.workerPool, new TaskQueue(), deployment, closeFuture, disableTCCL ? null : tccl); + } + @Override - public EventLoopContext createEventLoopContext(Deployment deployment, CloseFuture closeFuture, WorkerPool workerPool, ClassLoader tccl) { - return new EventLoopContext(this, eventLoopGroup.next(), internalWorkerPool, workerPool != null ? workerPool : this.workerPool, deployment, closeFuture, disableTCCL ? null : tccl); + public ContextImpl createEventLoopContext(Deployment deployment, CloseFuture closeFuture, WorkerPool workerPool, ClassLoader tccl) { + return createEventLoopContext(eventLoopGroup.next(), closeFuture, workerPool, deployment, tccl); } @Override - public EventLoopContext createEventLoopContext(EventLoop eventLoop, WorkerPool workerPool, ClassLoader tccl) { - return new EventLoopContext(this, eventLoop, internalWorkerPool, workerPool != null ? workerPool : this.workerPool, null, closeFuture, disableTCCL ? tccl : null); + public ContextImpl createEventLoopContext(EventLoop eventLoop, WorkerPool workerPool, ClassLoader tccl) { + return createEventLoopContext(eventLoop, closeFuture, workerPool, null, tccl); } @Override - public EventLoopContext createEventLoopContext() { + public ContextImpl createEventLoopContext() { return createEventLoopContext(null, closeFuture, null, Thread.currentThread().getContextClassLoader()); } + private ContextImpl createWorkerContext(EventLoop eventLoop, CloseFuture closeFuture, WorkerPool workerPool, Deployment deployment, ClassLoader tccl) { + TaskQueue orderedTasks = new TaskQueue(); + WorkerPool wp = workerPool != null ? workerPool : this.workerPool; + return new ContextImpl(this, ThreadingModel.WORKER, eventLoop, new WorkerExecutor(wp, orderedTasks), internalWorkerPool, wp, orderedTasks, deployment, closeFuture, disableTCCL ? null : tccl); + } + + @Override + public ContextInternal createWorkerContext(EventLoop eventLoop, WorkerPool workerPool, ClassLoader tccl) { + return createWorkerContext(eventLoop, closeFuture, workerPool, null, tccl); + } + @Override - public WorkerContext createWorkerContext(Deployment deployment, CloseFuture closeFuture, WorkerPool workerPool, ClassLoader tccl) { - return new WorkerContext(this, internalWorkerPool, workerPool != null ? workerPool : this.workerPool, deployment, closeFuture, disableTCCL ? null : tccl); + public ContextImpl createWorkerContext(Deployment deployment, CloseFuture closeFuture, WorkerPool workerPool, ClassLoader tccl) { + return createWorkerContext(eventLoopGroup.next(), closeFuture, workerPool, deployment, tccl); } @Override - public WorkerContext createWorkerContext() { + public ContextImpl createWorkerContext() { return createWorkerContext(null, closeFuture, null, Thread.currentThread().getContextClassLoader()); } + private ContextImpl createVirtualThreadContext(EventLoop eventLoop, CloseFuture closeFuture, Deployment deployment, ClassLoader tccl) { + if (!VertxInternal.isVirtualThreadAvailable()) { + throw new IllegalStateException("This Java runtime does not support virtual threads"); + } + TaskQueue orderedTasks = new TaskQueue(); + return new ContextImpl(this, ThreadingModel.VIRTUAL_THREAD, eventLoop, new WorkerExecutor(virtualThreaWorkerPool, orderedTasks), internalWorkerPool, virtualThreaWorkerPool, orderedTasks, deployment, closeFuture, disableTCCL ? null : tccl); + } + + @Override + public ContextImpl createVirtualThreadContext(Deployment deployment, CloseFuture closeFuture, ClassLoader tccl) { + return createVirtualThreadContext(eventLoopGroup.next(), closeFuture, deployment, tccl); + } + + @Override + public ContextImpl createVirtualThreadContext(EventLoop eventLoop, ClassLoader tccl) { + return createVirtualThreadContext(eventLoop, closeFuture, null, tccl); + } + + @Override + public ContextImpl createVirtualThreadContext() { + return createVirtualThreadContext(null, closeFuture, Thread.currentThread().getContextClassLoader()); + } + @Override public DnsClient createDnsClient(int port, String host) { return createDnsClient(new DnsClientOptions().setHost(host).setPort(port)); @@ -514,7 +618,7 @@ public DnsClient createDnsClient(DnsClientOptions options) { String host = options.getHost(); int port = options.getPort(); if (host == null || port < 0) { - DnsResolverProvider provider = new DnsResolverProvider(this, addressResolverOptions); + DnsAddressResolverProvider provider = DnsAddressResolverProvider.create(this, addressResolverOptions); InetSocketAddress address = provider.nameServerAddresses().get(0); // provide the host and port options = new DnsClientOptions(options) @@ -604,13 +708,13 @@ public synchronized Future close() { .close() .transform(ar -> deploymentManager.undeployAll()); if (haManager != null) { - fut = fut.transform(ar -> executeBlocking(res -> { + fut = fut.transform(ar -> executeBlocking(() -> { haManager.stop(); - res.complete(); + return null; }, false)); } fut = fut - .transform(ar -> addressResolver.close()) + .transform(ar -> hostnameResolver.close()) .transform(ar -> Future.future(h -> eventBus.close((Promise) h))) .transform(ar -> closeClusterManager()) .transform(ar -> { @@ -679,9 +783,9 @@ public Future undeploy(String deploymentID) { Future future; HAManager haManager = haManager(); if (haManager != null) { - future = this.executeBlocking(fut -> { + future = this.executeBlocking(() -> { haManager.removeFromHA(deploymentID); - fut.complete(); + return null; }, false); } else { future = getOrCreateContext().succeededFuture(); @@ -762,17 +866,25 @@ public File resolveFile(String fileName) { @Override public Future resolveAddress(String hostname) { - return addressResolver.resolveHostname(hostname); + return hostnameResolver.resolveHostname(hostname); + } + + @Override + public HostnameResolver hostnameResolver() { + return hostnameResolver; } @Override - public AddressResolver addressResolver() { - return addressResolver; + public DnsAddressResolverProvider dnsAddressResolverProvider(InetSocketAddress addr) { + AddressResolverOptions options = new AddressResolverOptions(addressResolverOptions); + options.setServers(Collections.singletonList(addr.getHostString() + ":" + addr.getPort())); + options.setOptResourceEnabled(false); + return DnsAddressResolverProvider.create(this, options); } @Override public AddressResolverGroup nettyAddressResolverGroup() { - return addressResolver.nettyAddressResolverGroup(); + return hostnameResolver.nettyAddressResolverGroup(); } @Override @@ -787,13 +899,9 @@ public BlockedThreadChecker blockedThreadChecker() { @SuppressWarnings("unchecked") private void deleteCacheDirAndShutdown(Promise promise) { - executeBlockingInternal(fut -> { - try { - fileResolver.close(); - fut.complete(); - } catch (IOException e) { - fut.tryFail(e); - } + executeBlockingInternal(() -> { + fileResolver.close(); + return null; }).onComplete(ar -> { workerPool.close(); internalWorkerPool.close(); @@ -801,6 +909,15 @@ private void deleteCacheDirAndShutdown(Promise promise) { for (WorkerPool workerPool : objects) { workerPool.close(); } + + if (virtualThreadExecutor != null) { + virtualThreadExecutor.shutdown(); + try { + virtualThreadExecutor.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException ignore) { + } + } + acceptorEventLoopGroup.shutdownGracefully(0, 10, TimeUnit.SECONDS).addListener(new GenericFutureListener() { @Override public void operationComplete(io.netty.util.concurrent.Future future) throws Exception { @@ -860,7 +977,11 @@ class InternalTimerHandler implements Handler, Closeable, Runnable { @Override public void run() { - context.emit(this); + ContextInternal dispatcher = context; + if (periodic) { + dispatcher = dispatcher.duplicate(); + } + dispatcher.emit(this); } public void handle(Void v) { @@ -960,6 +1081,12 @@ void close() { }; } + @Override + public WorkerPool wrapWorkerPool(ExecutorService executor) { + PoolMetrics workerMetrics = metrics != null ? metrics.createPoolMetrics("worker", null, -1) : null; + return new WorkerPool(executor, workerMetrics); + } + private static ThreadFactory createThreadFactory(VertxThreadFactory threadFactory, BlockedThreadChecker checker, Boolean useDaemonThread, long maxExecuteTime, TimeUnit maxExecuteTimeUnit, String prefix, boolean worker) { AtomicInteger threadCount = new AtomicInteger(0); return runnable -> { @@ -1047,7 +1174,7 @@ ContextInternal beginDispatch(ContextInternal context) { if (thread instanceof VertxThread) { VertxThread vertxThread = (VertxThread) thread; prev = vertxThread.context; - if (!ContextBase.DISABLE_TIMINGS) { + if (!ContextImpl.DISABLE_TIMINGS) { vertxThread.executeStart(); } vertxThread.context = context; @@ -1108,7 +1235,7 @@ void endDispatch(ContextInternal prev) { } Thread.currentThread().setContextClassLoader(tccl); } - if (!ContextBase.DISABLE_TIMINGS) { + if (!ContextImpl.DISABLE_TIMINGS) { vertxThread.executeEnd(); } } else { @@ -1130,4 +1257,24 @@ private void endDispatch2(ContextInternal prev) { Thread.currentThread().setContextClassLoader(tccl); } } -} + + /** + * Reads the version from the {@code vertx-version.txt} file. + * + * @return the version + */ + public static String version() { + if (version != null) { + return version; + } + try (InputStream is = VertxImpl.class.getClassLoader().getResourceAsStream("META-INF/vertx/vertx-version.txt")) { + if (is == null) { + throw new IllegalStateException("Cannot find vertx-version.txt on classpath"); + } + try (Scanner scanner = new Scanner(is, StandardCharsets.UTF_8).useDelimiter("\\A")) { + return version = scanner.hasNext() ? scanner.next().trim() : ""; + } + } catch (IOException e) { + throw new IllegalStateException(e.getMessage()); + } + }} diff --git a/src/main/java/io/vertx/core/impl/VertxInternal.java b/src/main/java/io/vertx/core/impl/VertxInternal.java index dfc545de694..fabf685f83f 100644 --- a/src/main/java/io/vertx/core/impl/VertxInternal.java +++ b/src/main/java/io/vertx/core/impl/VertxInternal.java @@ -16,6 +16,7 @@ import io.netty.channel.EventLoopGroup; import io.netty.resolver.AddressResolverGroup; import io.vertx.core.*; +import io.vertx.core.dns.impl.DnsAddressResolverProvider; import io.vertx.core.http.impl.HttpServerImpl; import io.vertx.core.impl.btc.BlockedThreadChecker; import io.vertx.core.impl.future.PromiseInternal; @@ -29,9 +30,12 @@ import io.vertx.core.spi.tracing.VertxTracer; import java.io.File; +import java.lang.ref.Cleaner; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.function.Function; @@ -45,6 +49,13 @@ */ public interface VertxInternal extends Vertx { + /** + * @return the Vert.x version + */ + static String version() { + return VertxImpl.version(); + } + /** * @return a promise associated with the context returned by {@link #getOrCreateContext()}. */ @@ -81,6 +92,8 @@ public interface VertxInternal extends Vertx { Transport transport(); + Cleaner cleaner(); + default C createSharedResource(String resourceKey, String resourceName, CloseFuture closeFuture, Function supplier) { return SharedResourceHolder.createSharedResource(this, resourceKey, resourceName, closeFuture, supplier); } @@ -94,18 +107,47 @@ default C createSharedResource(String resourceKey, String resourceName, Clos /** * @return event loop context */ - EventLoopContext createEventLoopContext(Deployment deployment, CloseFuture closeFuture, WorkerPool workerPool, ClassLoader tccl); + ContextInternal createEventLoopContext(Deployment deployment, CloseFuture closeFuture, WorkerPool workerPool, ClassLoader tccl); + + /** + * @return event loop context + */ + ContextInternal createEventLoopContext(EventLoop eventLoop, WorkerPool workerPool, ClassLoader tccl); + + /** + * @return event loop context + */ + ContextInternal createEventLoopContext(); + + /** + * @return worker context + */ + ContextInternal createWorkerContext(Deployment deployment, CloseFuture closeFuture, WorkerPool workerPool, ClassLoader tccl); + + /** + * @return worker context + */ + ContextInternal createWorkerContext(EventLoop eventLoop, WorkerPool workerPool, ClassLoader tccl); - EventLoopContext createEventLoopContext(EventLoop eventLoop, WorkerPool workerPool, ClassLoader tccl); + /** + * @return worker context + */ + ContextInternal createWorkerContext(); - EventLoopContext createEventLoopContext(); + /** + * @return virtual thread context + */ + ContextInternal createVirtualThreadContext(Deployment deployment, CloseFuture closeFuture, ClassLoader tccl); /** - * @return worker loop context + * @return virtual thread context */ - WorkerContext createWorkerContext(Deployment deployment, CloseFuture closeFuture, WorkerPool pool, ClassLoader tccl); + ContextInternal createVirtualThreadContext(EventLoop eventLoop, ClassLoader tccl); - WorkerContext createWorkerContext(); + /** + * @return virtual thread context + */ + ContextInternal createVirtualThreadContext(); @Override WorkerExecutorInternal createSharedWorkerExecutor(String name); @@ -121,6 +163,8 @@ default C createSharedResource(String resourceKey, String resourceName, Clos WorkerPool createSharedWorkerPool(String name, int poolSize, long maxExecuteTime, TimeUnit maxExecuteTimeUnit); + WorkerPool wrapWorkerPool(ExecutorService executor); + void simulateKill(); Deployment getDeployment(String deploymentID); @@ -133,12 +177,12 @@ default C createSharedResource(String resourceKey, String resourceName, Clos File resolveFile(String fileName); - default Future executeBlockingInternal(Handler> blockingCodeHandler) { + default Future executeBlockingInternal(Callable blockingCodeHandler) { ContextInternal context = getOrCreateContext(); return context.executeBlockingInternal(blockingCodeHandler); } - default Future executeBlockingInternal(Handler> blockingCodeHandler, boolean ordered) { + default Future executeBlockingInternal(Callable blockingCodeHandler, boolean ordered) { ContextInternal context = getOrCreateContext(); return context.executeBlockingInternal(blockingCodeHandler, ordered); } @@ -156,15 +200,24 @@ default Future executeBlockingInternal(Handler> blockingCodeHa Future resolveAddress(String hostname); /** - * @return the address resolver + * @return the default hostname resolver */ - AddressResolver addressResolver(); + HostnameResolver hostnameResolver(); + + DnsAddressResolverProvider dnsAddressResolverProvider(InetSocketAddress addr); /** * @return the file resolver */ FileResolver fileResolver(); + /** + * Return the Netty EventLoopGroup used by Vert.x + * + * @return the EventLoopGroup + */ + EventLoopGroup nettyEventLoopGroup(); + /** * @return the Netty {@code AddressResolverGroup} to use in a Netty {@code Bootstrap} */ @@ -183,4 +236,10 @@ default Future executeBlockingInternal(Handler> blockingCodeHa void removeCloseHook(Closeable hook); + /** + * @return whether virtual threads are available + */ + static boolean isVirtualThreadAvailable() { + return VertxImpl.VIRTUAL_THREAD_FACTORY != null; + } } diff --git a/src/main/java/io/vertx/core/impl/VertxWrapper.java b/src/main/java/io/vertx/core/impl/VertxWrapper.java index 6f7d6bae5d7..da340e13a1e 100644 --- a/src/main/java/io/vertx/core/impl/VertxWrapper.java +++ b/src/main/java/io/vertx/core/impl/VertxWrapper.java @@ -13,23 +13,15 @@ import io.netty.channel.EventLoop; import io.netty.channel.EventLoopGroup; import io.netty.resolver.AddressResolverGroup; -import io.vertx.core.Closeable; -import io.vertx.core.DeploymentOptions; -import io.vertx.core.Future; -import io.vertx.core.Handler; -import io.vertx.core.Promise; -import io.vertx.core.Verticle; -import io.vertx.core.Vertx; +import io.vertx.core.*; import io.vertx.core.datagram.DatagramSocket; import io.vertx.core.datagram.DatagramSocketOptions; import io.vertx.core.dns.DnsClient; import io.vertx.core.dns.DnsClientOptions; +import io.vertx.core.dns.impl.DnsAddressResolverProvider; import io.vertx.core.eventbus.EventBus; import io.vertx.core.file.FileSystem; -import io.vertx.core.http.HttpClient; -import io.vertx.core.http.HttpClientOptions; -import io.vertx.core.http.HttpServer; -import io.vertx.core.http.HttpServerOptions; +import io.vertx.core.http.*; import io.vertx.core.http.impl.HttpServerImpl; import io.vertx.core.impl.btc.BlockedThreadChecker; import io.vertx.core.impl.future.PromiseInternal; @@ -49,10 +41,13 @@ import io.vertx.core.spi.tracing.VertxTracer; import java.io.File; +import java.lang.ref.Cleaner; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.Map; import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.function.Supplier; @@ -82,39 +77,24 @@ public NetServer createNetServer(NetServerOptions options) { return delegate.createNetServer(options); } - @Override - public NetServer createNetServer() { - return delegate.createNetServer(); - } - @Override public NetClient createNetClient(NetClientOptions options) { return delegate.createNetClient(options); } - @Override - public NetClient createNetClient() { - return delegate.createNetClient(); - } - @Override public HttpServer createHttpServer(HttpServerOptions options) { return delegate.createHttpServer(options); } @Override - public HttpServer createHttpServer() { - return delegate.createHttpServer(); + public HttpClientBuilder httpClientBuilder() { + return delegate.httpClientBuilder(); } @Override - public HttpClient createHttpClient(HttpClientOptions options) { - return delegate.createHttpClient(options); - } - - @Override - public HttpClient createHttpClient() { - return delegate.createHttpClient(); + public WebSocketClient createWebSocketClient(WebSocketClientOptions options) { + return delegate.createWebSocketClient(options); } @Override @@ -122,11 +102,6 @@ public DatagramSocket createDatagramSocket(DatagramSocketOptions options) { return delegate.createDatagramSocket(options); } - @Override - public DatagramSocket createDatagramSocket() { - return delegate.createDatagramSocket(); - } - @Override public FileSystem fileSystem() { return delegate.fileSystem(); @@ -137,6 +112,11 @@ public EventBus eventBus() { return delegate.eventBus(); } + @Override + public DnsAddressResolverProvider dnsAddressResolverProvider(InetSocketAddress addr) { + return delegate.dnsAddressResolverProvider(addr); + } + @Override public DnsClient createDnsClient(int port, String host) { return delegate.createDnsClient(port, host); @@ -158,13 +138,13 @@ public SharedData sharedData() { } @Override - public long setTimer(long delay, Handler handler) { - return delegate.setTimer(delay, handler); + public Timer timer(long delay, TimeUnit unit) { + return delegate.timer(delay, unit); } @Override - public long setPeriodic(long delay, Handler handler) { - return delegate.setPeriodic(delay, handler); + public long setTimer(long delay, Handler handler) { + return delegate.setTimer(delay, handler); } @Override @@ -187,11 +167,6 @@ public Future close() { return delegate.close(); } - @Override - public Future deployVerticle(Verticle verticle) { - return delegate.deployVerticle(verticle); - } - @Override public Future deployVerticle(Verticle verticle, DeploymentOptions options) { return delegate.deployVerticle(verticle, options); @@ -207,11 +182,6 @@ public Future deployVerticle(Supplier verticleSupplier, Deploy return delegate.deployVerticle(verticleSupplier, options); } - @Override - public Future deployVerticle(String name) { - return delegate.deployVerticle(name); - } - @Override public Future deployVerticle(String name, DeploymentOptions options) { return delegate.deployVerticle(name, options); @@ -247,16 +217,6 @@ public boolean isClustered() { return delegate.isClustered(); } - @Override - public Future executeBlocking(Handler> blockingCodeHandler, boolean ordered) { - return delegate.executeBlocking(blockingCodeHandler, ordered); - } - - @Override - public Future executeBlocking(Handler> blockingCodeHandler) { - return delegate.executeBlocking(blockingCodeHandler); - } - @Override public EventLoopGroup nettyEventLoopGroup() { return delegate.nettyEventLoopGroup(); @@ -267,6 +227,11 @@ public boolean isNativeTransportEnabled() { return delegate.isNativeTransportEnabled(); } + @Override + public Throwable unavailableNativeTransportCause() { + return delegate.unavailableNativeTransportCause(); + } + @Override public Vertx exceptionHandler(Handler handler) { return delegate.exceptionHandler(handler); @@ -282,6 +247,11 @@ public PromiseInternal promise() { return delegate.promise(); } + @Override + public PromiseInternal promise(Promise promise) { + return delegate.promise(promise); + } + @Override public long maxEventLoopExecTime() { return delegate.maxEventLoopExecTime(); @@ -343,8 +313,8 @@ public Transport transport() { } @Override - public C createSharedResource(String resourceKey, String resourceName, CloseFuture closeFuture, Function supplier) { - return delegate.createSharedResource(resourceKey, resourceName, closeFuture, supplier); + public Cleaner cleaner() { + return delegate.cleaner(); } @Override @@ -353,27 +323,47 @@ public ContextInternal getContext() { } @Override - public EventLoopContext createEventLoopContext(Deployment deployment, CloseFuture closeFuture, WorkerPool workerPool, ClassLoader tccl) { + public ContextInternal createEventLoopContext(Deployment deployment, CloseFuture closeFuture, WorkerPool workerPool, ClassLoader tccl) { return delegate.createEventLoopContext(deployment, closeFuture, workerPool, tccl); } @Override - public EventLoopContext createEventLoopContext(EventLoop eventLoop, WorkerPool workerPool, ClassLoader tccl) { + public ContextInternal createEventLoopContext(EventLoop eventLoop, WorkerPool workerPool, ClassLoader tccl) { return delegate.createEventLoopContext(eventLoop, workerPool, tccl); } @Override - public EventLoopContext createEventLoopContext() { + public ContextInternal createEventLoopContext() { return delegate.createEventLoopContext(); } @Override - public WorkerContext createWorkerContext(Deployment deployment, CloseFuture closeFuture, WorkerPool pool, ClassLoader tccl) { - return delegate.createWorkerContext(deployment, closeFuture, pool, tccl); + public ContextInternal createVirtualThreadContext(Deployment deployment, CloseFuture closeFuture, ClassLoader tccl) { + return delegate.createVirtualThreadContext(deployment, closeFuture, tccl); } @Override - public WorkerContext createWorkerContext() { + public ContextInternal createVirtualThreadContext(EventLoop eventLoop, ClassLoader tccl) { + return delegate.createVirtualThreadContext(eventLoop, tccl); + } + + @Override + public ContextInternal createVirtualThreadContext() { + return delegate.createVirtualThreadContext(); + } + + @Override + public ContextInternal createWorkerContext(EventLoop eventLoop, WorkerPool workerPool, ClassLoader tccl) { + return delegate.createWorkerContext(eventLoop, workerPool, tccl); + } + + @Override + public ContextInternal createWorkerContext(Deployment deployment, CloseFuture closeFuture, WorkerPool workerPool, ClassLoader tccl) { + return delegate.createWorkerContext(deployment, closeFuture, workerPool, tccl); + } + + @Override + public ContextInternal createWorkerContext() { return delegate.createWorkerContext(); } @@ -402,6 +392,11 @@ public WorkerPool createSharedWorkerPool(String name, int poolSize, long maxExec return delegate.createSharedWorkerPool(name, poolSize, maxExecuteTime, maxExecuteTimeUnit); } + @Override + public WorkerPool wrapWorkerPool(ExecutorService executor) { + return delegate.wrapWorkerPool(executor); + } + @Override public void simulateKill() { delegate.simulateKill(); @@ -432,16 +427,6 @@ public File resolveFile(String fileName) { return delegate.resolveFile(fileName); } - @Override - public Future executeBlockingInternal(Handler> blockingCodeHandler) { - return delegate.executeBlockingInternal(blockingCodeHandler); - } - - @Override - public Future executeBlockingInternal(Handler> blockingCodeHandler, boolean ordered) { - return delegate.executeBlockingInternal(blockingCodeHandler, ordered); - } - @Override public ClusterManager getClusterManager() { return delegate.getClusterManager(); @@ -458,8 +443,8 @@ public Future resolveAddress(String hostname) { } @Override - public AddressResolver addressResolver() { - return delegate.addressResolver(); + public HostnameResolver hostnameResolver() { + return delegate.hostnameResolver(); } @Override diff --git a/src/main/java/io/vertx/core/impl/WorkerContext.java b/src/main/java/io/vertx/core/impl/WorkerContext.java deleted file mode 100644 index 29fe6eea27c..00000000000 --- a/src/main/java/io/vertx/core/impl/WorkerContext.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl; - -import io.vertx.core.Context; -import io.vertx.core.Handler; -import io.vertx.core.spi.metrics.PoolMetrics; - -import java.util.Objects; -import java.util.concurrent.Executor; -import java.util.concurrent.RejectedExecutionException; - -/** - * @author Tim Fox - */ -public class WorkerContext extends ContextBase { - - WorkerContext(VertxInternal vertx, - WorkerPool internalBlockingPool, - WorkerPool workerPool, - Deployment deployment, - CloseFuture closeFuture, - ClassLoader tccl) { - super(vertx, vertx.getEventLoopGroup().next(), internalBlockingPool, workerPool, deployment, closeFuture, tccl); - } - - @Override - protected void runOnContext(ContextInternal ctx, Handler action) { - try { - run(ctx, null, action); - } catch (RejectedExecutionException ignore) { - // Pool is already shut down - } - } - - /** - *
    - *
  • When the current thread is a worker thread of this context the implementation will execute the {@code task} directly
  • - *
  • Otherwise the task will be scheduled on the worker thread for execution
  • - *
- */ - @Override - protected void execute(ContextInternal ctx, T argument, Handler task) { - execute(orderedTasks, argument, task); - } - - @Override - protected void emit(ContextInternal ctx, T argument, Handler task) { - execute(orderedTasks, argument, arg -> { - ctx.dispatch(arg, task); - }); - } - - @Override - protected void execute(ContextInternal ctx, Runnable task) { - execute(this, task, Runnable::run); - } - - @Override - public boolean isEventLoopContext() { - return false; - } - - @Override - public boolean isWorkerContext() { - return true; - } - - private Executor executor; - - @Override - public Executor executor() { - if (executor == null) { - executor = command -> { - PoolMetrics metrics = workerPool.metrics(); - Object queueMetric = metrics != null ? metrics.submitted() : null; - orderedTasks.execute(() -> { - Object execMetric = null; - if (metrics != null) { - execMetric = metrics.begin(queueMetric); - } - try { - command.run(); - } finally { - if (metrics != null) { - metrics.end(execMetric, true); - } - } - }, workerPool.executor()); - }; - } - return executor; - } - - private void run(ContextInternal ctx, T value, Handler task) { - Objects.requireNonNull(task, "Task handler must not be null"); - executor().execute(() -> ctx.dispatch(value, task)); - } - - private void execute(TaskQueue queue, T argument, Handler task) { - if (Context.isOnWorkerThread()) { - task.handle(argument); - } else { - PoolMetrics metrics = workerPool.metrics(); - Object queueMetric = metrics != null ? metrics.submitted() : null; - queue.execute(() -> { - Object execMetric = null; - if (metrics != null) { - execMetric = metrics.begin(queueMetric); - } - try { - task.handle(argument); - } finally { - if (metrics != null) { - metrics.end(execMetric, true); - } - } - }, workerPool.executor()); - } - } - - @Override - public boolean inThread() { - return Context.isOnWorkerThread(); - } -} diff --git a/src/main/java/io/vertx/core/impl/WorkerExecutor.java b/src/main/java/io/vertx/core/impl/WorkerExecutor.java new file mode 100644 index 00000000000..067b5c4da88 --- /dev/null +++ b/src/main/java/io/vertx/core/impl/WorkerExecutor.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.impl; + +import io.vertx.core.Vertx; +import io.vertx.core.spi.metrics.PoolMetrics; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + +/** + * Execute events on a worker pool. + * + * @author Julien Viet + */ +public class WorkerExecutor implements EventExecutor { + + public static io.vertx.core.impl.WorkerExecutor unwrapWorkerExecutor() { + ContextInternal ctx = (ContextInternal) Vertx.currentContext(); + if (ctx != null) { + ctx = ctx.unwrap(); + Executor executor = ctx.executor(); + if (executor instanceof io.vertx.core.impl.WorkerExecutor) { + return (io.vertx.core.impl.WorkerExecutor) executor; + } else { + throw new IllegalStateException("Cannot be called on a Vert.x event-loop thread"); + } + } + // Technically it works also for worker threads but we don't want to encourage this + throw new IllegalStateException("Not running from a Vert.x virtual thread"); + } + + private final WorkerPool workerPool; + private final TaskQueue orderedTasks; + private final ThreadLocal inThread = new ThreadLocal<>(); + + public WorkerExecutor(WorkerPool workerPool, TaskQueue orderedTasks) { + this.workerPool = workerPool; + this.orderedTasks = orderedTasks; + } + + @Override + public boolean inThread() { + return inThread.get() == Boolean.TRUE; + } + + @Override + public void execute(Runnable command) { + PoolMetrics metrics = workerPool.metrics(); + Object queueMetric = metrics != null ? metrics.submitted() : null; + orderedTasks.execute(() -> { + Object execMetric = null; + if (metrics != null) { + execMetric = metrics.begin(queueMetric); + } + try { + inThread.set(true); + try { + command.run(); + } finally { + inThread.remove(); + } + } finally { + if (metrics != null) { + metrics.end(execMetric, true); + } + } + }, workerPool.executor()); + } + + /** + * See {@link TaskQueue#current()}. + */ + public TaskController current() { + return orderedTasks.current(); + } + + public interface TaskController { + + /** + * Resume the task, the {@code callback} will be executed when the task is resumed, before the task thread + * is unparked. + * + * @param callback called when the task is resumed + */ + void resume(Runnable callback); + + /** + * Like {@link #resume(Runnable)}. + */ + default void resume() { + resume(() -> {}); + } + + /** + * Suspend the task execution and park the current thread until the task is resumed. + * The next task in the queue will be executed, when there is one. + * + *

When the task wants to be resumed, it should call {@link #resume}, this will be executed immediately if there + * is no other tasks being executed, otherwise it will be added first in the queue. + */ + default void suspendAndAwaitResume() throws InterruptedException { + suspend().await(); + } + + /** + * Like {@link #suspendAndAwaitResume()} but does not await the task to be resumed. + * + * @return the latch to await + */ + CountDownLatch suspend(); + + } +} diff --git a/src/main/java/io/vertx/core/impl/WorkerExecutorImpl.java b/src/main/java/io/vertx/core/impl/WorkerExecutorImpl.java index 744fe6f7273..09716fb5c73 100644 --- a/src/main/java/io/vertx/core/impl/WorkerExecutorImpl.java +++ b/src/main/java/io/vertx/core/impl/WorkerExecutorImpl.java @@ -18,6 +18,7 @@ import io.vertx.core.spi.metrics.PoolMetrics; import java.lang.ref.Cleaner; +import java.util.concurrent.Callable; /** * @author Julien Viet @@ -56,10 +57,10 @@ public WorkerPool getPool() { } @Override - public Future<@Nullable T> executeBlocking(Handler> blockingCodeHandler, boolean ordered) { + public Future<@Nullable T> executeBlocking(Callable blockingCodeHandler, boolean ordered) { ContextInternal context = vertx.getOrCreateContext(); - ContextBase impl = context instanceof DuplicatedContext ? ((DuplicatedContext)context).delegate : (ContextBase) context; - return ContextBase.executeBlocking(context, blockingCodeHandler, pool, ordered ? impl.orderedTasks : null); + ContextImpl impl = context instanceof DuplicatedContext ? ((DuplicatedContext)context).delegate : (ContextImpl) context; + return ContextImpl.executeBlocking(context, blockingCodeHandler, pool, ordered ? impl.orderedTasks : null); } @Override diff --git a/src/main/java/io/vertx/core/impl/cpu/CpuCoreSensor.java b/src/main/java/io/vertx/core/impl/cpu/CpuCoreSensor.java index 7f0f2fce394..0a300725b2d 100644 --- a/src/main/java/io/vertx/core/impl/cpu/CpuCoreSensor.java +++ b/src/main/java/io/vertx/core/impl/cpu/CpuCoreSensor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -11,14 +11,13 @@ package io.vertx.core.impl.cpu; -import io.vertx.core.impl.launcher.commands.ExecUtils; - import java.io.*; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.AccessController; import java.security.PrivilegedAction; +import static io.vertx.core.impl.Utils.isLinux; + /** * Utility class providing the number of CPU cores available. On Linux, to handle CGroups, it reads and * parses the /proc/self/status file. @@ -62,7 +61,7 @@ private static int determineProcessors() { int fromJava = Runtime.getRuntime().availableProcessors(); int fromProcFile = 0; - if (!ExecUtils.isLinux()) { + if (!isLinux()) { return fromJava; } diff --git a/src/main/java/io/vertx/core/impl/future/Eventually.java b/src/main/java/io/vertx/core/impl/future/Eventually.java index 38b3ac09d5e..17b5011f9f3 100644 --- a/src/main/java/io/vertx/core/impl/future/Eventually.java +++ b/src/main/java/io/vertx/core/impl/future/Eventually.java @@ -14,6 +14,7 @@ import io.vertx.core.impl.ContextInternal; import java.util.function.Function; +import java.util.function.Supplier; /** * Eventually operation. @@ -22,18 +23,18 @@ */ class Eventually extends Operation implements Listener { - private final Function> mapper; + private final Supplier> supplier; - Eventually(ContextInternal context, Function> mapper) { + Eventually(ContextInternal context, Supplier> supplier) { super(context); - this.mapper = mapper; + this.supplier = supplier; } @Override public void onSuccess(T value) { FutureInternal future; try { - future = (FutureInternal) mapper.apply(null); + future = (FutureInternal) supplier.get(); } catch (Throwable e) { tryFail(e); return; @@ -54,7 +55,7 @@ public void onFailure(Throwable ignore) { public void onFailure(Throwable failure) { FutureInternal future; try { - future = (FutureInternal) mapper.apply(null); + future = (FutureInternal) supplier.get(); } catch (Throwable e) { tryFail(e); return; diff --git a/src/main/java/io/vertx/core/impl/future/FutureBase.java b/src/main/java/io/vertx/core/impl/future/FutureBase.java index 0d60b515949..cfd7a3e38d4 100644 --- a/src/main/java/io/vertx/core/impl/future/FutureBase.java +++ b/src/main/java/io/vertx/core/impl/future/FutureBase.java @@ -11,13 +11,20 @@ package io.vertx.core.impl.future; +import io.netty.channel.EventLoop; +import io.netty.util.concurrent.GlobalEventExecutor; +import io.netty.util.concurrent.OrderedEventExecutor; +import io.netty.util.concurrent.ScheduledFuture; import io.vertx.core.AsyncResult; import io.vertx.core.Future; -import io.vertx.core.Handler; +import io.vertx.core.Promise; import io.vertx.core.impl.ContextInternal; +import io.vertx.core.impl.NoStackTraceTimeoutException; import java.util.Objects; +import java.util.concurrent.TimeUnit; import java.util.function.Function; +import java.util.function.Supplier; /** * Future base implementation. @@ -94,9 +101,9 @@ public Future transform(Function, Future> mapper) { } @Override - public Future eventually(Function> mapper) { - Objects.requireNonNull(mapper, "No null mapper accepted"); - Eventually operation = new Eventually<>(context, mapper); + public Future eventually(Supplier> supplier) { + Objects.requireNonNull(supplier, "No null supplier accepted"); + Eventually operation = new Eventually<>(context, supplier); addListener(operation); return operation; } @@ -130,4 +137,39 @@ public Future otherwise(T value) { addListener(operation); return operation; } + + @Override + public Future timeout(long delay, TimeUnit unit) { + if (isComplete()) { + return this; + } + OrderedEventExecutor instance; + Promise promise; + if (context != null) { + instance = context.nettyEventLoop(); + promise = context.promise(); + } else { + instance = GlobalEventExecutor.INSTANCE; + promise = Promise.promise(); + } + ScheduledFuture task = instance.schedule(() -> { + String msg = "Timeout " + unit.toMillis(delay) + " (ms) fired"; + promise.fail(new NoStackTraceTimeoutException(msg)); + }, delay, unit); + addListener(new Listener() { + @Override + public void onSuccess(T value) { + if (task.cancel(false)) { + promise.complete(value); + } + } + @Override + public void onFailure(Throwable failure) { + if (task.cancel(false)) { + promise.fail(failure); + } + } + }); + return promise.future(); + } } diff --git a/src/main/java/io/vertx/core/impl/future/FutureImpl.java b/src/main/java/io/vertx/core/impl/future/FutureImpl.java index a737a0c01cf..98246bca1e0 100644 --- a/src/main/java/io/vertx/core/impl/future/FutureImpl.java +++ b/src/main/java/io/vertx/core/impl/future/FutureImpl.java @@ -25,7 +25,7 @@ * * @author Julien Viet */ -class FutureImpl extends FutureBase { +public class FutureImpl extends FutureBase { private static final Object NULL_VALUE = new Object(); @@ -35,14 +35,14 @@ class FutureImpl extends FutureBase { /** * Create a future that hasn't completed yet */ - FutureImpl() { + protected FutureImpl() { super(); } /** * Create a future that hasn't completed yet */ - FutureImpl(ContextInternal context) { + protected FutureImpl(ContextInternal context) { super(context); } @@ -127,6 +127,41 @@ public void onFailure(Throwable failure) { return this; } + @Override + public Future onComplete(Handler successHandler, Handler failureHandler) { + addListener(new Listener() { + @Override + public void onSuccess(T value) { + try { + if (successHandler != null) { + successHandler.handle(value); + } + } catch (Throwable t) { + if (context != null) { + context.reportException(t); + } else { + throw t; + } + } + } + @Override + public void onFailure(Throwable failure) { + try { + if (failureHandler != null) { + failureHandler.handle(failure); + } + } catch (Throwable t) { + if (context != null) { + context.reportException(t); + } else { + throw t; + } + } + } + }); + return this; + } + @Override public Future onComplete(Handler> handler) { Objects.requireNonNull(handler, "No null handler accepted"); diff --git a/src/main/java/io/vertx/core/impl/launcher/CommandLineUtils.java b/src/main/java/io/vertx/core/impl/launcher/CommandLineUtils.java deleted file mode 100644 index 4a7f66dbe71..00000000000 --- a/src/main/java/io/vertx/core/impl/launcher/CommandLineUtils.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl.launcher; - -import java.io.File; - -/** - * Utilities method to analyse original command line. - * - * @author Clement Escoffier - */ -public class CommandLineUtils { - - /** - * @return the fat-jar file used to execute the application if the fat-jar approach is used. - */ - public static String getJar() { - // Check whether or not the "sun.java.command" system property is defined, - // if it is, check whether the first segment of the command ends with ".jar". - String segment = getFirstSegmentOfCommand(); - if (segment != null && segment.endsWith(".jar")) { - return segment; - } else { - // Second attend is to check the classpath. If the classpath contains only one element, - // it's the fat jar - String classpath = System.getProperty("java.class.path"); - if (!classpath.isEmpty() && !classpath.contains(File.pathSeparator) && classpath.endsWith(".jar")) { - return classpath; - } - } - - return null; - - } - - /** - * @return try to get the command line having launched the application using the {@code sun.java.command} - * system properties. {@code null} if not set. - */ - public static String getCommand() { - return System.getProperty("sun.java.command"); - } - - /** - * @return the first segment of the command line. - */ - public static String getFirstSegmentOfCommand() { - String cmd = getCommand(); - if (cmd != null) { - String[] segments = cmd.split(" "); - if (segments.length >= 1) { - return segments[0]; - } - } - return null; - } -} diff --git a/src/main/java/io/vertx/core/impl/launcher/ServiceCommandFactoryLoader.java b/src/main/java/io/vertx/core/impl/launcher/ServiceCommandFactoryLoader.java deleted file mode 100644 index 819ebcd29a1..00000000000 --- a/src/main/java/io/vertx/core/impl/launcher/ServiceCommandFactoryLoader.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl.launcher; - -import io.vertx.core.ServiceHelper; -import io.vertx.core.spi.launcher.CommandFactory; -import io.vertx.core.spi.launcher.CommandFactoryLookup; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * Looks for command factories using a service loader. - * - * @author Clement Escoffier - */ -public class ServiceCommandFactoryLoader implements CommandFactoryLookup { - - private Collection commands; - - /** - * Creates a new instance of {@link ServiceCommandFactoryLoader} using the classloader having loaded the - * {@link ServiceCommandFactoryLoader} class. - */ - public ServiceCommandFactoryLoader() { - this.commands = ServiceHelper.loadFactories(CommandFactory.class, getClass().getClassLoader()); - } - - /** - * Creates a new instance of {@link ServiceCommandFactoryLoader} using specified classloader. - */ - public ServiceCommandFactoryLoader(ClassLoader loader) { - this.commands = ServiceHelper.loadFactories(CommandFactory.class, loader); - } - - @Override - public Collection> lookup() { - List> list = new ArrayList<>(); - commands.stream().forEach(list::add); - return list; - } - -} diff --git a/src/main/java/io/vertx/core/impl/launcher/VertxCommandLauncher.java b/src/main/java/io/vertx/core/impl/launcher/VertxCommandLauncher.java deleted file mode 100644 index 7acfcf8df1c..00000000000 --- a/src/main/java/io/vertx/core/impl/launcher/VertxCommandLauncher.java +++ /dev/null @@ -1,479 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl.launcher; - -import io.vertx.core.cli.*; -import io.vertx.core.cli.annotations.CLIConfigurator; -import io.vertx.core.impl.launcher.commands.RunCommand; -import io.vertx.core.spi.launcher.*; - -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintStream; -import java.net.URL; -import java.util.*; -import java.util.function.Supplier; -import java.util.jar.Attributes; -import java.util.jar.Manifest; -import java.util.stream.Collectors; - -/** - * The entry point of the Vert.x Command Line interface. - * - * @author Clement Escoffier - */ -public class VertxCommandLauncher extends UsageMessageFormatter { - - protected static List PROCESS_ARGS; - - /** - * @return the process argument. Verticles can use this method to retrieve the arguments. - */ - public static List getProcessArguments() { - return PROCESS_ARGS; - } - - /** - * the list of lookups. - */ - protected final List lookups; - - /** - * the list of commands. Sub-classes can decide to remove commands by removing entries from this map. - */ - protected final Map commandByName; - - /** - * the {@code Main-Class} object. - */ - protected Object main; - - /** - * Handles a command registration. - */ - public static class CommandRegistration { - public final CommandFactory factory; - public final CLI cli; - private List commands = new ArrayList<>(); - - public CommandRegistration(CommandFactory factory) { - this(factory, factory.define()); - } - - public CommandRegistration(CommandFactory factory, CLI cli) { - this.factory = factory; - this.cli = cli; - } - - public void addCommand(Command command) { - commands.add(command); - } - - public Command getCommand() { - if (!commands.isEmpty()) { - return commands.get(0); - } - return null; - } - - public List getCommands() { - return commands; - } - } - - /** - * Creates a new {@link VertxCommandLauncher} using the default {@link ServiceCommandFactoryLoader}. It uses the - * classloader having loaded {@link ServiceCommandFactoryLoader}. - */ - public VertxCommandLauncher() { - this(Collections.singletonList(new ServiceCommandFactoryLoader())); - } - - /** - * Creates a new {@link VertxCommandLauncher} using the given list of {@link CommandFactoryLookup}. - * - * @param lookups the list of lookup - */ - public VertxCommandLauncher(Collection lookups) { - this.lookups = new ArrayList<>(lookups); - this.commandByName = new TreeMap<>(); - load(); - } - - /** - * Loads the command. This method is {@link protected} to let sub-classes change the set of command or how - * they are loaded. - */ - protected void load() { - for (CommandFactoryLookup lookup : lookups) { - Collection> commands = lookup.lookup(); - commands.forEach(factory -> { - CLI cli = factory.define(); - CommandRegistration previous = commandByName.get(cli.getName()); - if (previous == null) { - commandByName.put(cli.getName(), new CommandRegistration(factory, cli)); - } else { - // command already registered, in this case we will replace IFF the priority is higher - if (cli.getPriority() > previous.cli.getPriority()) { - commandByName.put(cli.getName(), new CommandRegistration(factory, cli)); - } - } - }); - } - } - - public VertxCommandLauncher register(CommandFactory factory) { - CLI cli = factory.define(); - commandByName.put(cli.getName(), new CommandRegistration(factory, cli)); - return this; - } - - @Deprecated - @SuppressWarnings("unchecked") - public VertxCommandLauncher register(Class clazz) { - DefaultCommandFactory factory = new DefaultCommandFactory(clazz); - CLI cli = factory.define(); - commandByName.put(cli.getName(), new CommandRegistration(factory, cli)); - return this; - } - - @SuppressWarnings("unchecked") - public VertxCommandLauncher register(Class clazz, Supplier supplier) { - DefaultCommandFactory factory = new DefaultCommandFactory(clazz, supplier); - CLI cli = factory.define(); - commandByName.put(cli.getName(), new CommandRegistration(factory, cli)); - return this; - } - - public VertxCommandLauncher unregister(String name) { - commandByName.remove(name); - return this; - } - - /** - * @return the list of command. - */ - public Collection getCommandNames() { - return commandByName.keySet(); - } - - /** - * Creates a new {@link Command} instance. Sub-classes can change how {@link Command} instance are created. - * - * @param name the command name - * @param commandLine the command line - * @return the new instance, {@code null} if the command cannot be found. - */ - protected Command getNewCommandInstance(String name, CommandLine commandLine) { - CommandRegistration registration = commandByName.get(name); - if (registration != null) { - Command command = registration.factory.create(commandLine); - registration.addCommand(command); - return command; - } - return null; - } - - /** - * Gets an existing instance of command. - * - * @param name the command name - * @return the {@link Command} instance, {@code null} if not found - */ - public Command getExistingCommandInstance(String name) { - CommandRegistration registration = commandByName.get(name); - if (registration != null) { - return registration.getCommand(); - } - return null; - } - - /** - * Executes the given command. - * - * @param command the command name - * @param cla the arguments - */ - public void execute(String command, String... cla) { - if (command != null && isAskingForVersion(command)) { - execute("version"); - return; - } - - if (command == null || isAskingForHelp(command)) { - printGlobalUsage(); - return; - } - - CommandRegistration registration = commandByName.get(command); - if (registration == null) { - printCommandNotFound(command); - return; - } - - CLI cli = registration.cli; - - try { - // Check for help - the command need to have been initialized ot get the complete model. - if (cla.length >= 1 && isAskingForHelp(cla[0])) { - printCommandUsage(cli); - return; - } - - // Step 1 - parsing and injection - CommandLine evaluated = cli.parse(Arrays.asList(cla)); - Command cmd = getNewCommandInstance(command, evaluated); - ExecutionContext context = new ExecutionContext(cmd, this, evaluated); - if (main != null) { - context.put("Main", main); - context.put("Main-Class", main.getClass().getName()); - context.put("Default-Verticle-Factory", getFromManifest("Default-Verticle-Factory")); - } - - CLIConfigurator.inject(evaluated, cmd); - - // Step 2 - validation - cmd.setUp(context); - - // Step 3 - execution - cmd.run(); - - // Step 4 - cleanup - cmd.tearDown(); - } catch (MissingOptionException | MissingValueException | InvalidValueException e) { - printSpecificException(cli, e); - } catch (CLIException e) { - printGenericExecutionError(cli, e); - } catch (RuntimeException e) { - if (e.getCause() instanceof CLIException) { - printGenericExecutionError(cli, (CLIException) e.getCause()); - return; - } - throw e; - } - } - - - protected void printCommandUsage(CLI cli) { - StringBuilder builder = new StringBuilder(); - cli.usage(builder, getCommandLinePrefix()); - getPrintStream().println(builder.toString()); - } - - protected void printGenericExecutionError(CLI cli, CLIException e) { - getPrintStream().println("Error while executing command " + cli.getName() + ": " + e.getMessage() + getNewLine()); - if (e.getCause() != null) { - e.getCause().printStackTrace(getPrintStream()); - } - } - - protected void printSpecificException(CLI cli, Exception e) { - getPrintStream().println(e.getMessage() + getNewLine()); - printCommandUsage(cli); - } - - protected void printCommandNotFound(String command) { - StringBuilder builder = new StringBuilder(); - buildWrapped(builder, 0, "The command '" + command + "' is not a valid command." + getNewLine() - + "See '" + getCommandLinePrefix() + " --help'"); - getPrintStream().println(builder.toString()); - } - - protected void printGlobalUsage() { - StringBuilder builder = new StringBuilder(); - - computeUsage(builder, getCommandLinePrefix() + " [COMMAND] [OPTIONS] [arg...]"); - - builder.append(getNewLine()); - builder.append("Commands:").append(getNewLine()); - - renderCommands(builder, commandByName.values().stream().map(r -> r.cli).collect(Collectors.toList())); - - builder.append(getNewLine()).append(getNewLine()); - - buildWrapped(builder, 0, "Run '" + getCommandLinePrefix() + " COMMAND --help' for more information on a command."); - - getPrintStream().println(builder.toString()); - } - - protected String getCommandLinePrefix() { - // Check whether `vertx.cli.usage.prefix` is set, if so use it. This system property let scripts configure the value - // displayed by the usage, even if they are calling java. - String sysProp = System.getProperty("vertx.cli.usage.prefix"); - if (sysProp != null) { - return sysProp; - } - - String jar = CommandLineUtils.getJar(); - if (jar != null) { - return "java -jar " + jar; - } - String command = CommandLineUtils.getFirstSegmentOfCommand(); - if (command != null) { - return "java " + command; - } - - return "vertx"; - } - - protected static boolean isAskingForHelp(String command) { - return command.equalsIgnoreCase("--help") - || command.equalsIgnoreCase("-help") - || command.equalsIgnoreCase("-h") - || command.equalsIgnoreCase("?") - || command.equalsIgnoreCase("/?"); - } - - protected static boolean isAskingForVersion(String command) { - return command.equalsIgnoreCase("-version") || command.equalsIgnoreCase("--version"); - } - - /** - * Dispatches to the right command. This method is generally called from the {@code main} method. - * - * @param args the command line arguments. - */ - public void dispatch(String[] args) { - dispatch(null, args); - } - - /** - * Dispatches to the right command. This method is generally called from the {@code main} method. - * - * @param main the main instance on which hooks and callbacks are going to be called. If not set, the current - * object is used. - * @param args the command line arguments. - */ - public void dispatch(Object main, String[] args) { - this.main = main == null ? this : main; - PROCESS_ARGS = Collections.unmodifiableList(Arrays.asList(args)); - - // Several cases need to be detected here. - // The first argument may be "--help" => must display help message - // The first argument may be "--version" => must execute the version command. - // The first argument may be a command and the second "--help" => display command usage - // The first argument may be a command => command execution - // If the first argument is not a command, try to see if there is a given main verticle and execute the default - // command with the arguments (prepended with the main verticle). - // Finally, we have two fallbacks - // - if no args (and so no main verticle) - display usage - // - if args has been set, display command usage. - - - if (args.length >= 1 && isAskingForHelp(args[0])) { - printGlobalUsage(); - return; - } - - if (args.length >= 1 && isAskingForVersion(args[0])) { - execute("version"); - return; - } - - if (args.length >= 1 && commandByName.get(args[0]) != null) { - execute(args[0], Arrays.copyOfRange(args, 1, args.length)); - return; - } - - if (args.length >= 2 && isAskingForHelp(args[1])) { - execute(args[0], "--help"); - return; - } - - // We check whether or not we have a main verticle specified via the getMainVerticle method. - // By default this method retrieve the value from the 'Main-Verticle' Manifest header. However it can be overridden. - - String verticle = getMainVerticle(); - String command = getCommandFromManifest(); - if (verticle != null) { - // We have a main verticle, append it to the arg list and execute the default command (run) - String[] newArgs = new String[args.length + 1]; - newArgs[0] = verticle; - System.arraycopy(args, 0, newArgs, 1, args.length); - execute(getDefaultCommand(), newArgs); - return; - } else if (command != null) { - execute(command, args); - return; - } - - // Fallbacks - if (args.length == 0) { - printGlobalUsage(); - } else { - // compatibility support - if (args[0].equalsIgnoreCase("-ha")) { - execute("bare", Arrays.copyOfRange(args, 1, args.length)); - } else { - printCommandNotFound(args[0]); - } - } - } - - /** - * @return the default command if specified in the {@code MANIFEST}, "run" if not found. - */ - protected String getDefaultCommand() { - String fromManifest = getCommandFromManifest(); - if (fromManifest == null) { - return "run"; - } - return fromManifest; - } - - protected String getCommandFromManifest() { - return getFromManifest("Main-Command"); - } - - private String getFromManifest(String key) { - try { - Enumeration resources = RunCommand.class.getClassLoader().getResources("META-INF/MANIFEST.MF"); - while (resources.hasMoreElements()) { - try (InputStream stream = resources.nextElement().openStream()) { - Manifest manifest = new Manifest(stream); - Attributes attributes = manifest.getMainAttributes(); - String mainClass = attributes.getValue("Main-Class"); - if (main.getClass().getName().equals(mainClass)) { - String value = attributes.getValue(key); - if (value != null) { - return value; - } - } - } - } - } catch (IOException e) { - throw new IllegalStateException(e.getMessage()); - } - return null; - } - - /** - * @return the printer used to write the messages. Defaults to {@link System#out}. - */ - public PrintStream getPrintStream() { - return System.out; - } - - /** - * @return the main verticle, {@code null} if not found. - */ - protected String getMainVerticle() { - return getFromManifest("Main-Verticle"); - } - - /** - * For testing purpose only - reset the process arguments - */ - public static void resetProcessArguments() { - PROCESS_ARGS = null; - } -} diff --git a/src/main/java/io/vertx/core/impl/launcher/VertxLifecycleHooks.java b/src/main/java/io/vertx/core/impl/launcher/VertxLifecycleHooks.java deleted file mode 100644 index 4549e2cc02a..00000000000 --- a/src/main/java/io/vertx/core/impl/launcher/VertxLifecycleHooks.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl.launcher; - -import io.vertx.core.DeploymentOptions; -import io.vertx.core.Vertx; -import io.vertx.core.VertxOptions; -import io.vertx.core.json.JsonObject; - -/** - * Interface that let sub-classes of launcher to be notified on different events. - * - * @author Clement Escoffier - */ -public interface VertxLifecycleHooks { - - /** - * Hook for sub-classes of the {@link io.vertx.core.Launcher} class before the vertx instance is started. Options - * can still be updated. - * - * @param config the json config file passed via -conf on the command line, an empty json object is not set. - */ - void afterConfigParsed(JsonObject config); - - /** - * Hook for sub-classes of the {@link io.vertx.core.Launcher} class before the vertx instance is started. Options - * can still be updated. - * - * @param options the vert.x options - */ - void beforeStartingVertx(VertxOptions options); - - /** - * Hook for sub-classes of the {@link io.vertx.core.Launcher} class after the vertx instance is started. - * - * @param vertx the vert.x instance - */ - void afterStartingVertx(Vertx vertx); - - /** - * Hook for sub classes of the {@link io.vertx.core.Launcher} class before the verticle is deployed. Deployment - * options can still be updated. - * - * @param deploymentOptions the deployment options - */ - void beforeDeployingVerticle(DeploymentOptions deploymentOptions); - - /** - * Hook for sub classes of the {@link io.vertx.core.Launcher} class called before the {@link Vertx} instance is - * terminated. The hook is called during the {@link Vertx#close()} method. - * - * @param vertx the {@link Vertx} instance, cannot be {@code null} - */ - void beforeStoppingVertx(Vertx vertx); - - /** - * Hook for sub classes of the {@link io.vertx.core.Launcher} class called after the {@link Vertx} instance has been - * terminated. The hook is called after the {@link Vertx#close()} method. - */ - void afterStoppingVertx(); - - /** - * A deployment failure has been encountered. You can override this method to customize the behavior. - * - * @param vertx the vert.x instance - * @param mainVerticle the main verticle name - * @param deploymentOptions the deployment options - * @param cause the cause - */ - void handleDeployFailed(Vertx vertx, String mainVerticle, DeploymentOptions deploymentOptions, - Throwable cause); - -} diff --git a/src/main/java/io/vertx/core/impl/launcher/commands/BareCommand.java b/src/main/java/io/vertx/core/impl/launcher/commands/BareCommand.java deleted file mode 100644 index e4ce4ba7cfe..00000000000 --- a/src/main/java/io/vertx/core/impl/launcher/commands/BareCommand.java +++ /dev/null @@ -1,503 +0,0 @@ -/* - * Copyright (c) 2011-2021 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl.launcher.commands; - -import io.vertx.core.AsyncResult; -import io.vertx.core.Vertx; -import io.vertx.core.VertxException; -import io.vertx.core.VertxOptions; -import io.vertx.core.cli.annotations.*; -import io.vertx.core.eventbus.AddressHelper; -import io.vertx.core.eventbus.EventBusOptions; -import io.vertx.core.impl.VertxBuilder; -import io.vertx.core.impl.launcher.VertxLifecycleHooks; -import io.vertx.core.impl.logging.Logger; -import io.vertx.core.json.DecodeException; -import io.vertx.core.json.JsonObject; -import io.vertx.core.spi.launcher.ExecutionContext; - -import java.io.File; -import java.io.FileNotFoundException; -import java.lang.reflect.Method; -import java.util.Enumeration; -import java.util.Objects; -import java.util.Properties; -import java.util.Scanner; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -/** - * Command to create a bare instance of vert.x. - * - * @author Clement Escoffier - */ -@Summary("Creates a bare instance of vert.x.") -@Description("This command launches a vert.x instance but do not deploy any verticles. It will " + - "receive a verticle if another node of the cluster dies.") -@Name("bare") -public class BareCommand extends ClasspathHandler { - - public static final String VERTX_OPTIONS_PROP_PREFIX = "vertx.options."; - public static final String VERTX_EVENTBUS_PROP_PREFIX = "vertx.eventBus.options."; - public static final String DEPLOYMENT_OPTIONS_PROP_PREFIX = "vertx.deployment.options."; - public static final String METRICS_OPTIONS_PROP_PREFIX = "vertx.metrics.options."; - - protected Vertx vertx; - - protected int clusterPort; - protected String clusterHost; - protected int clusterPublicPort; - protected String clusterPublicHost; - - protected int quorum; - protected String haGroup; - - protected String vertxOptions; - protected VertxOptions options; - - protected Runnable finalAction; - - /** - * Sets the quorum option. - * - * @param quorum the quorum, default to 1. - */ - @Option(longName = "quorum", argName = "q") - @Description("Used in conjunction with -ha this specifies the minimum number of nodes in the cluster for any HA " + - "deploymentIDs to be active. Defaults to 1.") - @DefaultValue("-1") - public void setQuorum(int quorum) { - this.quorum = quorum; - } - - /** - * Sets the HA group name. - * - * @param group the name of the group, default to {@code __DEFAULT__}. - */ - @Option(longName = "hagroup", argName = "group") - @Description("used in conjunction with -ha this specifies the HA group this node will join. There can be multiple " + - "HA groups in a cluster. Nodes will only failover to other nodes in the same group. Defaults to '__DEFAULT__'.") - @DefaultValue("__DEFAULT__") - public void setHAGroup(String group) { - this.haGroup = group; - } - - /** - * Sets the cluster port. - * - * @param port the port - */ - @Option(longName = "cluster-port", argName = "port") - @Description("Port to use for cluster communication. Default is 0 which means choose a spare random port.") - @DefaultValue("0") - public void setClusterPort(int port) { - this.clusterPort = port; - } - - /** - * Sets the cluster host. - * - * @param host the cluster host - */ - @Option(longName = "cluster-host", argName = "host") - @Description("host to bind to for cluster communication. If this is not specified vert.x will attempt to choose one" + - " from the available interfaces.") - public void setClusterHost(String host) { - this.clusterHost = host; - } - - /** - * Sets the cluster public port. - * - * @param port the port - */ - @Option(longName = "cluster-public-port", argName = "public-port") - @Description("Public port to use for cluster communication. Default is -1 which means same as cluster port.") - @DefaultValue("-1") - public void setClusterPublicPort(int port) { - this.clusterPublicPort = port; - } - - /** - * Sets the cluster public host. - * - * @param host the host - */ - @Option(longName = "cluster-public-host", argName = "public-host") - @Description("Public host to bind to for cluster communication. If not specified, Vert.x will use the same as cluster host.") - public void setClusterPublicHost(String host) { - this.clusterPublicHost = host; - } - - /** - * The Vert.x options, it can be a json file or a json string. - * - * @param vertxOptions the configuration - */ - @Option(longName = "options", argName = "options") - @Description("Specifies the Vert.x options. It should reference either a JSON file which represents the options OR be a JSON string.") - public void setVertxOptions(String vertxOptions) { - if (vertxOptions != null) { - // For inlined configuration remove first and end single and double quotes if any - this.vertxOptions = vertxOptions.trim() - .replaceAll("^\"|\"$", "") - .replaceAll("^'|'$", ""); - } else { - this.vertxOptions = null; - } - } - - /** - * @return whether or not the vert.x instance should be clustered. This implementation - * returns {@code true}. - */ - public boolean isClustered() { - return true; - } - - /** - * @return whether or not the vert.x instance should be launched in high-availability mode. This - * implementation returns {@code true}. - */ - public boolean getHA() { - return true; - } - - /** - * Starts the vert.x instance. - */ - @Override - public void run() { - this.run(null); - } - - /** - * Starts the vert.x instance and sets the final action (called when vert.x is closed). - * - * @param action the action, can be {@code null} - */ - public void run(Runnable action) { - this.finalAction = action; - vertx = startVertx(); - } - - /** - * Starts the vert.x instance. - * - * @return the created instance of vert.x - */ - @SuppressWarnings("ThrowableResultOfMethodCallIgnored") - protected Vertx startVertx() { - JsonObject optionsJson = getJsonFromFileOrString(vertxOptions, "options"); - - EventBusOptions eventBusOptions; - VertxBuilder builder; - if (optionsJson == null) { - eventBusOptions = getEventBusOptions(); - builder = new VertxBuilder(); - } else { - eventBusOptions = getEventBusOptions(optionsJson.getJsonObject("eventBusOptions")); - builder = new VertxBuilder(optionsJson); - } - options = builder.options(); - options.setEventBusOptions(eventBusOptions); - - beforeStartingVertx(options); - - configureFromSystemProperties.set(log); - try { - configureFromSystemProperties(options, VERTX_OPTIONS_PROP_PREFIX); - if (options.getMetricsOptions() != null) { - configureFromSystemProperties(options.getMetricsOptions(), METRICS_OPTIONS_PROP_PREFIX); - } - builder.init(); - } finally { - configureFromSystemProperties.set(null); - } - - Vertx instance; - if (isClustered()) { - log.info("Starting clustering..."); - eventBusOptions = options.getEventBusOptions(); - if (!Objects.equals(eventBusOptions.getHost(), EventBusOptions.DEFAULT_CLUSTER_HOST)) { - clusterHost = eventBusOptions.getHost(); - } - if (eventBusOptions.getPort() != EventBusOptions.DEFAULT_CLUSTER_PORT) { - clusterPort = eventBusOptions.getPort(); - } - if (!Objects.equals(eventBusOptions.getClusterPublicHost(), EventBusOptions.DEFAULT_CLUSTER_PUBLIC_HOST)) { - clusterPublicHost = eventBusOptions.getClusterPublicHost(); - } - if (eventBusOptions.getClusterPublicPort() != EventBusOptions.DEFAULT_CLUSTER_PUBLIC_PORT) { - clusterPublicPort = eventBusOptions.getClusterPublicPort(); - } - - eventBusOptions.setHost(clusterHost) - .setPort(clusterPort) - .setClusterPublicHost(clusterPublicHost); - if (clusterPublicPort != -1) { - eventBusOptions.setClusterPublicPort(clusterPublicPort); - } - if (getHA()) { - options.setHAEnabled(true); - if (haGroup != null) { - options.setHAGroup(haGroup); - } - if (quorum != -1) { - options.setQuorumSize(quorum); - } - } - - CountDownLatch latch = new CountDownLatch(1); - AtomicReference> result = new AtomicReference<>(); - createClustered(builder).onComplete(ar -> { - result.set(ar); - latch.countDown(); - }); - try { - if (!latch.await(2, TimeUnit.MINUTES)) { - log.error("Timed out in starting clustered Vert.x"); - return null; - } - } catch (InterruptedException e) { - log.error("Thread interrupted in startup"); - Thread.currentThread().interrupt(); - return null; - } - if (result.get().failed()) { - log.error("Failed to form cluster", result.get().cause()); - return null; - } - instance = result.get().result(); - } else { - instance = create(builder); - } - addShutdownHook(instance, log, finalAction); - afterStartingVertx(instance); - return instance; - } - - protected JsonObject getJsonFromFileOrString(String jsonFileOrString, String argName) { - JsonObject conf; - if (jsonFileOrString != null) { - try (Scanner scanner = new Scanner(new File(jsonFileOrString), "UTF-8").useDelimiter("\\A")) { - String sconf = scanner.next(); - try { - conf = new JsonObject(sconf); - } catch (DecodeException e) { - log.error("Configuration file " + sconf + " does not contain a valid JSON object"); - return null; - } - } catch (FileNotFoundException e) { - try { - conf = new JsonObject(jsonFileOrString); - } catch (DecodeException e2) { - // The configuration is not printed for security purpose, it can contain sensitive data. - log.error("The -" + argName + " argument does not point to an existing file or is not a valid JSON object", e2); - return null; - } - } - } else { - conf = null; - } - return conf; - } - - /** - * Hook called after starting vert.x. - * - * @param instance the created vert.x instance. - */ - protected void afterStartingVertx(Vertx instance) { - Object main = executionContext.main(); - if (main instanceof VertxLifecycleHooks) { - ((VertxLifecycleHooks) main).afterStartingVertx(instance); - } - } - - /** - * Hook called before starting vert.x. - * - * @param options the deployment options - */ - protected void beforeStartingVertx(VertxOptions options) { - Object main = executionContext.main(); - if (main instanceof VertxLifecycleHooks) { - ((VertxLifecycleHooks) main).beforeStartingVertx(options); - } - } - - /** - * @return the event bus options. - */ - protected EventBusOptions getEventBusOptions() { - return getEventBusOptions(null); - } - - /** - * @return the event bus options. - */ - protected EventBusOptions getEventBusOptions(JsonObject jsonObject) { - EventBusOptions eventBusOptions = jsonObject == null ? new EventBusOptions() : new EventBusOptions(jsonObject); - configureFromSystemProperties.set(log); - try { - configureFromSystemProperties(eventBusOptions, VERTX_EVENTBUS_PROP_PREFIX); - } finally { - configureFromSystemProperties.set(null); - } - return eventBusOptions; - } - - private static final ThreadLocal configureFromSystemProperties = new ThreadLocal<>(); - - /** - * This is used as workaround to retain the existing behavior of the Vert.x CLI and won't be executed - * in other situation. - */ - public static void configureFromSystemProperties(Object options, String prefix) { - Logger log = configureFromSystemProperties.get(); - if (log == null) { - return; - } - Properties props = System.getProperties(); - Enumeration e = props.propertyNames(); - // Uhh, properties suck - while (e.hasMoreElements()) { - String propName = (String) e.nextElement(); - String propVal = props.getProperty(propName); - if (propName.startsWith(prefix)) { - String fieldName = propName.substring(prefix.length()); - Method setter = getSetter(fieldName, options.getClass()); - if (setter == null) { - log.warn("No such property to configure on options: " + options.getClass().getName() + "." + fieldName); - continue; - } - Class argType = setter.getParameterTypes()[0]; - Object arg; - try { - if (argType.equals(String.class)) { - arg = propVal; - } else if (argType.equals(int.class)) { - arg = Integer.valueOf(propVal); - } else if (argType.equals(long.class)) { - arg = Long.valueOf(propVal); - } else if (argType.equals(boolean.class)) { - arg = Boolean.valueOf(propVal); - } else if (argType.isEnum()){ - arg = Enum.valueOf((Class)argType, propVal); - } else { - log.warn("Invalid type for setter: " + argType); - continue; - } - } catch (IllegalArgumentException e2) { - log.warn("Invalid argtype:" + argType + " on options: " + options.getClass().getName() + "." + fieldName); - continue; - } - try { - setter.invoke(options, arg); - } catch (Exception ex) { - throw new VertxException("Failed to invoke setter: " + setter, ex); - } - } - } - } - - private static Method getSetter(String fieldName, Class clazz) { - Method[] meths = clazz.getDeclaredMethods(); - for (Method meth : meths) { - if (("set" + fieldName).equalsIgnoreCase(meth.getName())) { - return meth; - } - } - - // This set contains the overridden methods - meths = clazz.getMethods(); - for (Method meth : meths) { - if (("set" + fieldName).equalsIgnoreCase(meth.getName())) { - return meth; - } - } - - return null; - } - - /** - * Registers a shutdown hook closing the given vert.x instance when the JVM is terminating. - * Optionally, an action can be executed after the termination of the {@link Vertx} instance. - * - * @param vertx the vert.x instance, must not be {@code null} - * @param log the log, must not be {@code null} - * @param action the action, may be {@code null} - */ - protected static void addShutdownHook(Vertx vertx, Logger log, Runnable action) { - Runtime.getRuntime().addShutdownHook(new Thread(getTerminationRunnable(vertx, log, action))); - } - - /** - * Gets the termination runnable used to close the Vert.x instance. - * - * @param vertx the vert.x instance, must not be {@code null} - * @param log the log, must not be {@code null} - * @param action the action, may be {@code null} - */ - public static Runnable getTerminationRunnable(Vertx vertx, Logger log, Runnable action) { - return () -> { - CountDownLatch latch = new CountDownLatch(1); - if (vertx != null) { - vertx.close().onComplete(ar -> { - if (!ar.succeeded()) { - log.error("Failure in stopping Vert.x", ar.cause()); - } - latch.countDown(); - }); - try { - if (!latch.await(2, TimeUnit.MINUTES)) { - log.error("Timed out waiting to undeploy all"); - } - if (action != null) { - action.run(); - } - } catch (InterruptedException e) { - throw new IllegalStateException(e); - } - } - }; - } - - /** - * @return Get default interface to use since the user hasn't specified one. - * @deprecated as of 4.0, this method is no longer used. - */ - @Deprecated - protected String getDefaultAddress() { - return AddressHelper.defaultAddress(); - } - - - /** - * For testing purpose only. - * - * @param context the context to inject for testing. - */ - public void setExecutionContext(ExecutionContext context) { - this.executionContext = context; - } - - /** - * @return the vert.x instance if created. - */ - public synchronized Vertx vertx() { - return vertx; - } -} diff --git a/src/main/java/io/vertx/core/impl/launcher/commands/BareCommandFactory.java b/src/main/java/io/vertx/core/impl/launcher/commands/BareCommandFactory.java deleted file mode 100644 index e1cbb74a8b0..00000000000 --- a/src/main/java/io/vertx/core/impl/launcher/commands/BareCommandFactory.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl.launcher.commands; - -import io.vertx.core.spi.launcher.DefaultCommandFactory; - -/** - * Factory to create the {@code bare} command. - * - * @author Clement Escoffier - */ -public class BareCommandFactory extends DefaultCommandFactory { - - /** - * Creates a new instance of {@link BareCommandFactory}. - */ - public BareCommandFactory() { - super(BareCommand.class, BareCommand::new); - } -} diff --git a/src/main/java/io/vertx/core/impl/launcher/commands/ClasspathHandler.java b/src/main/java/io/vertx/core/impl/launcher/commands/ClasspathHandler.java deleted file mode 100644 index 2c4115e4f1d..00000000000 --- a/src/main/java/io/vertx/core/impl/launcher/commands/ClasspathHandler.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl.launcher.commands; - -import io.vertx.core.*; -import io.vertx.core.cli.annotations.Description; -import io.vertx.core.cli.annotations.Option; -import io.vertx.core.impl.VertxBuilder; -import io.vertx.core.impl.logging.Logger; -import io.vertx.core.impl.logging.LoggerFactory; -import io.vertx.core.spi.launcher.DefaultCommand; - -import java.io.File; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * Command using the classpath option should extends this class as it manages the interaction with the - * custom classloader. - * - * @author Clement Escoffier - */ -public abstract class ClasspathHandler extends DefaultCommand { - - protected static final String PATH_SEP = System.getProperty("path.separator"); - - protected final Logger log = LoggerFactory.getLogger(this.getClass()); - - protected List classpath; - - protected Object manager; - private ClassLoader classloader; - - /** - * Sets the classpath. - * - * @param classpath the classpath - */ - @Option(shortName = "cp", longName = "classpath", argName = "classpath") - @Description("Provides an extra classpath to be used for the verticle deployment.") - public void setClasspath(String classpath) { - if (classpath == null || classpath.isEmpty()) { - this.classloader = ClasspathHandler.class.getClassLoader(); - this.classpath = Collections.emptyList(); - } else { - this.classpath = Arrays.asList(classpath.split(PATH_SEP)); - this.classloader = createClassloader(); - } - } - - /** - * Creates a classloader respecting the classpath option. - * - * @return the classloader. - */ - protected synchronized ClassLoader createClassloader() { - URL[] urls = classpath.stream().map(path -> { - File file = new File(path); - try { - return file.toURI().toURL(); - } catch (MalformedURLException e) { - throw new IllegalStateException(e); - } - }).toArray(URL[]::new); - return new URLClassLoader(urls, this.getClass().getClassLoader()); - } - - /** - * Creates a new instance of {@link VertxIsolatedDeployer}. - * - * @return the new instance. - */ - protected synchronized Object newInstance() { - try { - classloader = (classpath == null || classpath.isEmpty()) ? - ClasspathHandler.class.getClassLoader() : createClassloader(); - Class clazz = classloader.loadClass("io.vertx.core.impl.launcher.commands.VertxIsolatedDeployer"); - return clazz.getDeclaredConstructor().newInstance(); - } catch (Exception e) { - log.error("Failed to load or instantiate the isolated deployer", e); - throw new IllegalStateException(e); - } - } - - /** - * Creates a new non-clustered vert.x instance. - * - * @param builder the builder - * @return the created instance - */ - protected synchronized Vertx create(VertxBuilder builder) { - final ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); - try { - Thread.currentThread().setContextClassLoader(classloader != null ? classloader : getClass().getClassLoader()); - return builder.vertx(); - } catch (Exception e) { - log.error("Failed to create the vert.x instance", e); - } finally { - Thread.currentThread().setContextClassLoader(originalClassLoader); - } - return null; - } - - /** - * Creates a new clustered vert.x instance. - * - * @param builder the builder - * @return a future notified with the result - */ - protected synchronized Future createClustered(VertxBuilder builder) { - final ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); - try { - Thread.currentThread().setContextClassLoader(classloader != null ? classloader : getClass().getClassLoader()); - return builder.clusteredVertx(); - } catch (Exception e) { - log.error("Failed to create the vert.x instance", e); - return Future.failedFuture(e); - } finally { - Thread.currentThread().setContextClassLoader(originalClassLoader); - } - } - - /** - * Deploys the given verticle using the given deployment options. - * - * @param verticle the verticle - * @param vertx the vert.x instance - * @param options the deployment options - * @param completionHandler the completion handler - */ - public synchronized void deploy(String verticle, Vertx vertx, DeploymentOptions options, - Handler> completionHandler) { - if (manager == null) { - manager = newInstance(); - } - - final ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); - try { - Thread.currentThread().setContextClassLoader(classloader); - Method method = manager.getClass().getMethod("deploy", String.class, Vertx.class, DeploymentOptions.class, - Handler.class); - - if (executionContext.get("Default-Verticle-Factory") != null) { - // there is a configured default - if (verticle.indexOf(':') == -1) { - // and the verticle is not using a explicit factory - verticle = executionContext.get("Default-Verticle-Factory") + ":" + verticle; - } - } - - method.invoke(manager, verticle, vertx, options, completionHandler); - } catch (InvocationTargetException e) { - log.error("Failed to deploy verticle " + verticle, e.getCause()); - } catch (Exception e) { - log.error("Failed to deploy verticle " + verticle, e); - } finally { - Thread.currentThread().setContextClassLoader(originalClassLoader); - } - } -} diff --git a/src/main/java/io/vertx/core/impl/launcher/commands/ExecUtils.java b/src/main/java/io/vertx/core/impl/launcher/commands/ExecUtils.java deleted file mode 100644 index b94f820a589..00000000000 --- a/src/main/java/io/vertx/core/impl/launcher/commands/ExecUtils.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl.launcher.commands; - -import io.vertx.core.impl.Utils; - -import java.util.List; - -/** - * A couple of utility methods easing process creation. - * - * @author Clement Escoffier - */ -public class ExecUtils { - - private static final String SINGLE_QUOTE = "\'"; - private static final String DOUBLE_QUOTE = "\""; - - - // A note about exit code - // * 0 for success - // * 1 for general error - // * exit codes 1-2, 126-165, and 255 have special meanings, and should therefore be avoided for user-specified - // exit parameters - // * Ending with exit 127 would certainly cause confusion when troubleshooting as it's the command not found code - // * 130, 134, 133, 135, 137, 140, 129, 142, 143, 145, 146, 149, 150, 152, 154, 155, 158 are used by the JVM, and - // so have special meanings - // * it's better to avoid 3-9 - - /** - * Error code used when vert.x cannot be initialized. - */ - public static final int VERTX_INITIALIZATION_EXIT_CODE = 11; - - /** - * Error code used when a deployment failed. - */ - public static final int VERTX_DEPLOYMENT_EXIT_CODE = 15; - - /** - * Error code used when a spawn process cannot be found, created, or stopped smoothly. - */ - public static final int PROCESS_ERROR_EXIT_CODE = 12; - - /** - * Error code used when the system configuration does not satisfy the requirements (java not found for example). - */ - public static final int SYSTEM_CONFIGURATION_EXIT_CODE = 14; - - /** - * The {@code os.name} property is mandatory (from the Java Virtual Machine specification). - */ - private static String osName = System.getProperty("os.name").toLowerCase(); - - /** - * Puts quotes around the given String if necessary. - *

- * If the argument doesn't include spaces or quotes, return it as is. If it - * contains double quotes, use single quotes - else surround the argument by - * double quotes. - *

- * - * @param argument the argument to be quoted - * @return the quoted argument - * @throws IllegalArgumentException If argument contains both types of quotes - */ - public static String quoteArgument(final String argument) { - - String cleanedArgument = argument.trim(); - - // strip the quotes from both ends - while (cleanedArgument.startsWith(SINGLE_QUOTE) && cleanedArgument.endsWith(SINGLE_QUOTE) - || cleanedArgument.startsWith(DOUBLE_QUOTE) && cleanedArgument.endsWith(DOUBLE_QUOTE)) { - cleanedArgument = cleanedArgument.substring(1, cleanedArgument.length() - 1); - } - - final StringBuilder buf = new StringBuilder(); - if (cleanedArgument.contains(DOUBLE_QUOTE)) { - if (cleanedArgument.contains(SINGLE_QUOTE)) { - throw new IllegalArgumentException( - "Can't handle single and double quotes in same argument"); - } - if (Utils.isWindows()) { - return buf.append(DOUBLE_QUOTE).append(cleanedArgument.replace("\"", "\\\"")).append(DOUBLE_QUOTE).toString(); - } else { - return buf.append(SINGLE_QUOTE).append(cleanedArgument).append( - SINGLE_QUOTE).toString(); - } - } else if (cleanedArgument.contains(SINGLE_QUOTE) - || cleanedArgument.contains(" ")) { - return buf.append(DOUBLE_QUOTE).append(cleanedArgument).append( - DOUBLE_QUOTE).toString(); - } else { - return cleanedArgument; - } - } - - /** - * Adds an argument to the given list. It automatically adds quotes to the argument if necessary. - * - * @param args the list of arguments - * @param argument the argument to add - */ - public static void addArgument(List args, String argument) { - args.add(quoteArgument(argument)); - } - - /** - * @return {@code true} if the current operating system belongs to the "windows" family. - */ - public static boolean isWindows() { - return osName.contains("windows"); - } - - /** - * @return {@code true} if the current operating system belongs to the "linux" family. - */ - public static boolean isLinux() { - return osName.contains("nux"); - } - - /** - * Exits the JVM with the given exit code. - * - * @param code the code, {@code 0} for success. By convention a non zero value if return to denotes an - * error. - */ - public static void exit(int code) { - System.exit(code); - } - - /** - * Exits the JVM and indicate an issue during the Vert.x initialization. - */ - public static void exitBecauseOfVertxInitializationIssue() { - exit(VERTX_INITIALIZATION_EXIT_CODE); - } - - /** - * Exits the JVM and indicate an issue during the deployment of the main verticle. - */ - public static void exitBecauseOfVertxDeploymentIssue() { - exit(VERTX_DEPLOYMENT_EXIT_CODE); - } - - /** - * Exits the JVM and indicate an issue with a process creation or termination. - */ - public static void exitBecauseOfProcessIssue() { - exit(PROCESS_ERROR_EXIT_CODE); - } - - /** - * Exits the JVM and indicate an issue with the system configuration. - */ - public static void exitBecauseOfSystemConfigurationIssue() { - exit(SYSTEM_CONFIGURATION_EXIT_CODE); - } - - -} diff --git a/src/main/java/io/vertx/core/impl/launcher/commands/FileSelector.java b/src/main/java/io/vertx/core/impl/launcher/commands/FileSelector.java deleted file mode 100644 index 1adc7951439..00000000000 --- a/src/main/java/io/vertx/core/impl/launcher/commands/FileSelector.java +++ /dev/null @@ -1,365 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl.launcher.commands; - - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.StringTokenizer; - -/** - * Utility methods to test path matching. This is used by the {@link Watcher} to determine whether or not a file - * triggers a redeployment. - * - * @author Clement Escoffier - */ -public final class FileSelector { - - /** - * When the pattern starts with a {@link File#separator}, {@code str} has to start with a {@link File#separator}. - * - * @return {@code true} when @{code str} starts with a {@link File#separator}, and {@code pattern} starts with a - * {@link File#separator}. - */ - private static boolean separatorPatternStartSlashMismatch(String pattern, String str, String separator) { - return str.startsWith(separator) != pattern.startsWith(separator); - } - - /** - * Tests whether or not a given path matches a given pattern. - * - * @param pattern The pattern to match against. Must not be - * {@code null}. - * @param str The path to match, as a String. Must not be - * {@code null}. - * @return {@code true} if the pattern matches against the string, - * or {@code false} otherwise. - */ - public static boolean matchPath(String pattern, String str) { - return matchPath(pattern, str, true); - } - - /** - * Tests whether or not a given path matches a given pattern. - * - * @param pattern The pattern to match against. Must not be - * {@code null}. - * @param str The path to match, as a String. Must not be - * {@code null}. - * @param isCaseSensitive Whether or not matching should be performed - * case sensitively. - * @return {@code true} if the pattern matches against the string, - * or {@code false} otherwise. - */ - public static boolean matchPath(String pattern, String str, boolean isCaseSensitive) { - return matchPath(pattern, str, File.separator, isCaseSensitive); - } - - protected static boolean matchPath(String pattern, String str, String separator, boolean isCaseSensitive) { - return matchPathPattern(pattern, str, separator, isCaseSensitive); - } - - private static boolean matchPathPattern(String pattern, String str, String separator, boolean isCaseSensitive) { - if (separatorPatternStartSlashMismatch(pattern, str, separator)) { - return false; - } - String[] patDirs = tokenizePathToString(pattern, separator); - String[] strDirs = tokenizePathToString(str, separator); - return matchPathPattern(patDirs, strDirs, isCaseSensitive); - - } - - private static boolean matchPathPattern(String[] patDirs, String[] strDirs, boolean isCaseSensitive) { - int patIdxStart = 0; - int patIdxEnd = patDirs.length - 1; - int strIdxStart = 0; - int strIdxEnd = strDirs.length - 1; - - // up to first '**' - while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) { - String patDir = patDirs[patIdxStart]; - if (patDir.equals("**")) { - break; - } - if (!match(patDir, strDirs[strIdxStart], isCaseSensitive)) { - return false; - } - patIdxStart++; - strIdxStart++; - } - if (strIdxStart > strIdxEnd) { - // String is exhausted - for (int i = patIdxStart; i <= patIdxEnd; i++) { - if (!patDirs[i].equals("**")) { - return false; - } - } - return true; - } else { - if (patIdxStart > patIdxEnd) { - // String not exhausted, but pattern is. Failure. - return false; - } - } - - // up to last '**' - while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) { - String patDir = patDirs[patIdxEnd]; - if (patDir.equals("**")) { - break; - } - if (!match(patDir, strDirs[strIdxEnd], isCaseSensitive)) { - return false; - } - patIdxEnd--; - strIdxEnd--; - } - if (strIdxStart > strIdxEnd) { - // String is exhausted - for (int i = patIdxStart; i <= patIdxEnd; i++) { - if (!patDirs[i].equals("**")) { - return false; - } - } - return true; - } - - while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) { - int patIdxTmp = -1; - for (int i = patIdxStart + 1; i <= patIdxEnd; i++) { - if (patDirs[i].equals("**")) { - patIdxTmp = i; - break; - } - } - if (patIdxTmp == patIdxStart + 1) { - // '**/**' situation, so skip one - patIdxStart++; - continue; - } - // Find the pattern between padIdxStart & padIdxTmp in str between - // strIdxStart & strIdxEnd - int patLength = (patIdxTmp - patIdxStart - 1); - int strLength = (strIdxEnd - strIdxStart + 1); - int foundIdx = -1; - strLoop: - for (int i = 0; i <= strLength - patLength; i++) { - for (int j = 0; j < patLength; j++) { - String subPat = patDirs[patIdxStart + j + 1]; - String subStr = strDirs[strIdxStart + i + j]; - if (!match(subPat, subStr, isCaseSensitive)) { - continue strLoop; - } - } - - foundIdx = strIdxStart + i; - break; - } - - if (foundIdx == -1) { - return false; - } - - patIdxStart = patIdxTmp; - strIdxStart = foundIdx + patLength; - } - - for (int i = patIdxStart; i <= patIdxEnd; i++) { - if (!patDirs[i].equals("**")) { - return false; - } - } - - return true; - } - - /** - * Tests whether or not a string matches against a pattern. - * The pattern may contain two special characters:
- * '*' means zero or more characters
- * '?' means one and only one character - * - * @param pattern The pattern to match against. - * Must not be{@code null}. - * @param str The string which must be matched against the pattern. - * Must not be{@code null}. - * @return {@code true} if the string matches against the pattern, - * or {@code false} otherwise. - */ - public static boolean match(String pattern, String str) { - return match(pattern, str, true); - } - - /** - * Tests whether or not a string matches against a pattern. - * The pattern may contain two special characters:
- * '*' means zero or more characters
- * '?' means one and only one character - * - * @param pattern The pattern to match against. - * Must not be{@code null}. - * @param str The string which must be matched against the pattern. - * Must not be{@code null}. - * @param isCaseSensitive Whether or not matching should be performed - * case sensitively. - * @return {@code true} if the string matches against the pattern, - * or {@code false} otherwise. - */ - public static boolean match(String pattern, String str, boolean isCaseSensitive) { - char[] patArr = pattern.toCharArray(); - char[] strArr = str.toCharArray(); - return match(patArr, strArr, isCaseSensitive); - } - - private static boolean match(char[] patArr, char[] strArr, boolean isCaseSensitive) { - int patIdxStart = 0; - int patIdxEnd = patArr.length - 1; - int strIdxStart = 0; - int strIdxEnd = strArr.length - 1; - char ch; - - boolean containsStar = false; - for (char aPatArr : patArr) { - if (aPatArr == '*') { - containsStar = true; - break; - } - } - - if (!containsStar) { - // No '*'s, so we make a shortcut - if (patIdxEnd != strIdxEnd) { - return false; // Pattern and string do not have the same size - } - for (int i = 0; i <= patIdxEnd; i++) { - ch = patArr[i]; - if (ch != '?' && !equals(ch, strArr[i], isCaseSensitive)) { - return false; // Character mismatch - } - } - return true; // String matches against pattern - } - - if (patIdxEnd == 0) { - return true; // Pattern contains only '*', which matches anything - } - - // Process characters before first star - while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) { - if (ch != '?' && !equals(ch, strArr[strIdxStart], isCaseSensitive)) { - return false; // Character mismatch - } - patIdxStart++; - strIdxStart++; - } - if (strIdxStart > strIdxEnd) { - return checkOnlyStartsLeft(patArr, patIdxStart, patIdxEnd); - } - - // Process characters after last star - while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) { - if (ch != '?' && !equals(ch, strArr[strIdxEnd], isCaseSensitive)) { - return false; // Character mismatch - } - patIdxEnd--; - strIdxEnd--; - } - if (strIdxStart > strIdxEnd) { - // All characters in the string are used. Check if only '*'s are - // left in the pattern. If so, we succeeded. Otherwise failure. - return checkOnlyStartsLeft(patArr, patIdxStart, patIdxEnd); - } - - // process pattern between stars. padIdxStart and patIdxEnd point - // always to a '*'. - while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) { - int patIdxTmp = -1; - for (int i = patIdxStart + 1; i <= patIdxEnd; i++) { - if (patArr[i] == '*') { - patIdxTmp = i; - break; - } - } - if (patIdxTmp == patIdxStart + 1) { - // Two stars next to each other, skip the first one. - patIdxStart++; - continue; - } - // Find the pattern between padIdxStart & padIdxTmp in str between - // strIdxStart & strIdxEnd - int patLength = (patIdxTmp - patIdxStart - 1); - int strLength = (strIdxEnd - strIdxStart + 1); - int foundIdx = -1; - strLoop: - for (int i = 0; i <= strLength - patLength; i++) { - for (int j = 0; j < patLength; j++) { - ch = patArr[patIdxStart + j + 1]; - if (ch != '?' && !equals(ch, strArr[strIdxStart + i + j], isCaseSensitive)) { - continue strLoop; - } - } - - foundIdx = strIdxStart + i; - break; - } - - if (foundIdx == -1) { - return false; - } - - patIdxStart = patIdxTmp; - strIdxStart = foundIdx + patLength; - } - - // All characters in the string are used. Check if only '*'s are left - // in the pattern. If so, we succeeded. Otherwise failure. - return checkOnlyStartsLeft(patArr, patIdxStart, patIdxEnd); - } - - private static boolean checkOnlyStartsLeft(char[] patArr, int patIdxStart, int patIdxEnd) { - // All characters in the string are used. Check if only '*'s are - // left in the pattern. If so, we succeeded. Otherwise failure. - for (int i = patIdxStart; i <= patIdxEnd; i++) { - if (patArr[i] != '*') { - return false; - } - } - return true; - } - - /** - * Tests whether two characters are equal. - */ - private static boolean equals(char c1, char c2, boolean isCaseSensitive) { - if (c1 == c2) { - return true; - } - if (!isCaseSensitive) { - // NOTE: Try both upper case and lower case as done by String.equalsIgnoreCase() - if (Character.toUpperCase(c1) == Character.toUpperCase(c2) - || Character.toLowerCase(c1) == Character.toLowerCase(c2)) { - return true; - } - } - return false; - } - - private static String[] tokenizePathToString(String path, String separator) { - List ret = new ArrayList<>(); - StringTokenizer st = new StringTokenizer(path, separator); - while (st.hasMoreTokens()) { - ret.add(st.nextToken()); - } - return ret.toArray(new String[0]); - } -} diff --git a/src/main/java/io/vertx/core/impl/launcher/commands/ListCommand.java b/src/main/java/io/vertx/core/impl/launcher/commands/ListCommand.java deleted file mode 100644 index c8d1b1748b5..00000000000 --- a/src/main/java/io/vertx/core/impl/launcher/commands/ListCommand.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl.launcher.commands; - - -import io.vertx.core.cli.annotations.Description; -import io.vertx.core.cli.annotations.Name; -import io.vertx.core.cli.annotations.Summary; -import io.vertx.core.spi.launcher.DefaultCommand; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * A command listing launched vert.x instances. Instances are found using the `vertx.id` indicator in the - * process list. - * - * @author Clement Escoffier - */ -@Name("list") -@Summary("List vert.x applications") -@Description("List all vert.x applications launched with the `start` command") -public class ListCommand extends DefaultCommand { - - private final static Pattern PS = Pattern.compile("-Dvertx.id=(.*)\\s*"); - - private final static Pattern FAT_JAR_EXTRACTION = Pattern.compile("-jar (\\S*)"); - - private final static Pattern VERTICLE_EXTRACTION = Pattern.compile("run (\\S*)"); - - // Note about stack traces - the stack trace are printed on the stream passed to the command. - - /** - * Executes the {@code list} command. - */ - @Override - public void run() { - out.println("Listing vert.x applications..."); - List cmd = new ArrayList<>(); - if (!ExecUtils.isWindows()) { - try { - cmd.add("sh"); - cmd.add("-c"); - cmd.add("ps ax | grep \"vertx.id=\""); - - dumpFoundVertxApplications(cmd); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - e.printStackTrace(out); - } catch (Exception e) { - e.printStackTrace(out); - } - - } else { - try { - // Use wmic. - cmd.add("WMIC"); - cmd.add("PROCESS"); - cmd.add("WHERE"); - cmd.add("CommandLine like '%java.exe%'"); - cmd.add("GET"); - cmd.add("CommandLine"); - cmd.add("/VALUE"); - - dumpFoundVertxApplications(cmd); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - e.printStackTrace(out); - } catch (Exception e) { - e.printStackTrace(out); - } - } - - - } - - private void dumpFoundVertxApplications(List cmd) throws IOException, InterruptedException { - boolean none = true; - final Process process = new ProcessBuilder(cmd).start(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { - String line; - while ((line = reader.readLine()) != null) { - final Matcher matcher = PS.matcher(line); - if (matcher.find()) { - String id = matcher.group(1); - String details = extractApplicationDetails(line); - out.println(id + "\t" + details); - none = false; - } - } - process.waitFor(); - } - if (none) { - out.println("No vert.x application found."); - } - } - - /** - * Tries to extract the fat jar name of the verticle name. It's a best-effort approach looking at the name of the - * jar or to the verticle name from the command line. If not found, no details are returned (empty string). - * - * @return the details, empty if it cannot be extracted. - */ - protected static String extractApplicationDetails(String line) { - Matcher matcher = FAT_JAR_EXTRACTION.matcher(line); - if (matcher.find()) { - return matcher.group(1); - } else { - matcher = VERTICLE_EXTRACTION.matcher(line); - if (matcher.find()) { - return matcher.group(1); - } - } - // No details. - return ""; - } -} diff --git a/src/main/java/io/vertx/core/impl/launcher/commands/ListCommandFactory.java b/src/main/java/io/vertx/core/impl/launcher/commands/ListCommandFactory.java deleted file mode 100644 index a7f24042ab8..00000000000 --- a/src/main/java/io/vertx/core/impl/launcher/commands/ListCommandFactory.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl.launcher.commands; - -import io.vertx.core.spi.launcher.DefaultCommandFactory; - -/** - * Defines the `list` command. - * - * @author Clement Escoffier - */ -public class ListCommandFactory extends DefaultCommandFactory { - /** - * Creates a new {@link ListCommandFactory}. - */ - public ListCommandFactory() { - super(ListCommand.class, ListCommand::new); - } -} diff --git a/src/main/java/io/vertx/core/impl/launcher/commands/RunCommand.java b/src/main/java/io/vertx/core/impl/launcher/commands/RunCommand.java deleted file mode 100644 index 1c4c9a26896..00000000000 --- a/src/main/java/io/vertx/core/impl/launcher/commands/RunCommand.java +++ /dev/null @@ -1,454 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl.launcher.commands; - -import io.vertx.core.*; -import io.vertx.core.cli.CLIException; -import io.vertx.core.cli.CommandLine; -import io.vertx.core.cli.annotations.*; -import io.vertx.core.impl.VertxInternal; -import io.vertx.core.impl.launcher.VertxLifecycleHooks; -import io.vertx.core.json.JsonObject; -import io.vertx.core.spi.launcher.ExecutionContext; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; - -/** - * The vert.x run command that lets you execute verticles. - * - * @author Clement Escoffier - */ -@Name("run") -@Summary("Runs a verticle called in its own instance of vert.x.") -public class RunCommand extends BareCommand implements Closeable { - - protected DeploymentOptions deploymentOptions; - - protected boolean cluster; - protected boolean ha; - - protected int instances; - protected String config; - protected boolean worker; - - protected String mainVerticle; - protected List redeploy; - - - protected String vertxApplicationBackgroundId; - protected String onRedeployCommand; - protected Watcher watcher; - private long redeployScanPeriod; - private long redeployGracePeriod; - private long redeployTerminationPeriod; - - /** - * Enables / disables the high-availability. - * - * @param ha whether or not to enable the HA. - */ - @Option(longName = "ha", acceptValue = false, flag = true) - @Description("If specified the verticle will be deployed as a high availability (HA) deployment. This means it can " + - "fail over to any other nodes in the cluster started with the same HA group.") - public void setHighAvailability(boolean ha) { - this.ha = ha; - } - - /** - * Enables / disables the clustering. - * - * @param cluster whether or not to start vert.x in clustered mode. - */ - @Option(longName = "cluster", acceptValue = false, flag = true) - @Description("If specified then the vert.x instance will form a cluster with any other vert.x instances on the " + - "network.") - public void setCluster(boolean cluster) { - this.cluster = cluster; - } - - /** - * Whether or not the verticle is deployed as a worker verticle. - * - * @param worker {@code true} to deploy the verticle as worker, {@code false} otherwise - */ - @Option(longName = "worker", acceptValue = false) - @Description("If specified then the verticle is a worker verticle.") - public void setWorker(boolean worker) { - this.worker = worker; - } - - /** - * Sets the number of instance of the verticle to create. - * - * @param instances the number of instances - */ - @Option(longName = "instances", argName = "instances") - @DefaultValue("1") - @Description("Specifies how many instances of the verticle will be deployed. Defaults to 1.") - public void setInstances(int instances) { - this.instances = instances; - } - - /** - * The main verticle configuration, it can be a json file or a json string. - * - * @param configuration the configuration - */ - @Option(longName = "conf", argName = "config") - @Description("Specifies configuration that should be provided to the verticle. should reference either a " + - "text file containing a valid JSON object which represents the configuration OR be a JSON string.") - public void setConfig(String configuration) { - if (configuration != null) { - // For inlined configuration remove first and end single and double quotes if any - this.config = configuration.trim() - .replaceAll("^\"|\"$", "") - .replaceAll("^'|'$", ""); - } else { - this.config = null; - } - } - - /** - * Sets the main verticle that is deployed. - * - * @param verticle the verticle - */ - @Argument(index = 0, argName = "main-verticle", required = true) - @Description("The main verticle to deploy, it can be a fully qualified class name or a file.") - public void setMainVerticle(String verticle) { - this.mainVerticle = verticle; - } - - @Option(longName = "redeploy", argName = "includes") - @Description("Enable automatic redeployment of the application. This option takes a set on includes as parameter " + - "indicating which files need to be watched. Patterns are separated by a comma.") - @ParsedAsList - public void setRedeploy(List redeploy) { - this.redeploy = redeploy; - } - - /** - * Sets the user command executed during redeployment. - * - * @param command the on redeploy command - * @deprecated Use 'on-redeploy' instead. It will be removed in vert.x 3.3 - */ - @Option(longName = "onRedeploy", argName = "cmd") - @Description("Optional shell command executed when a redeployment is triggered (deprecated - will be removed in 3" + - ".3, use 'on-redeploy' instead") - @Hidden - @Deprecated - public void setOnRedeployCommandOld(String command) { - out.println("[WARNING] the 'onRedeploy' option is deprecated, and will be removed in vert.x 3.3. Use " + - "'on-redeploy' instead."); - setOnRedeployCommand(command); - } - - @Option(longName = "on-redeploy", argName = "cmd") - @Description("Optional shell command executed when a redeployment is triggered") - public void setOnRedeployCommand(String command) { - this.onRedeployCommand = command; - } - - @Option(longName = "redeploy-scan-period", argName = "period") - @Description("When redeploy is enabled, this option configures the file system scanning period to detect file " + - "changes. The time is given in milliseconds. 250 ms by default.") - @DefaultValue("250") - public void setRedeployScanPeriod(long period) { - this.redeployScanPeriod = period; - } - - @Option(longName = "redeploy-grace-period", argName = "period") - @Description("When redeploy is enabled, this option configures the grace period between 2 redeployments. The time " + - "is given in milliseconds. 1000 ms by default.") - @DefaultValue("1000") - public void setRedeployGracePeriod(long period) { - this.redeployGracePeriod = period; - } - - @Option(longName = "redeploy-termination-period", argName = "period") - @Description("When redeploy is enabled, this option configures the time waited to be sure that the previous " + - "version of the application has been stopped. It is useful on Windows, where the 'terminate' command may take time to be " + - "executed.The time is given in milliseconds. 0 ms by default.") - @DefaultValue("0") - public void setRedeployStopWaitingTime(long period) { - this.redeployTerminationPeriod = period; - } - - /** - * Validates the command line parameters. - * - * @param context - the execution context - * @throws CLIException - validation failed - */ - @Override - public void setUp(ExecutionContext context) throws CLIException { - super.setUp(context); - - CommandLine commandLine = executionContext.commandLine(); - if (!isClustered() && ( - commandLine.isOptionAssigned(executionContext.cli().getOption("cluster-host")) - || commandLine.isOptionAssigned(executionContext.cli().getOption("cluster-port")) - || commandLine.isOptionAssigned(executionContext.cli().getOption("cluster-public-host")) - || commandLine.isOptionAssigned(executionContext.cli().getOption("cluster-public-port")) - )) { - throw new CLIException("The -cluster-xxx options require -cluster to be enabled"); - } - - // If quorum and / or ha-group, ha need to have been explicitly set - io.vertx.core.cli.Option haGroupOption = executionContext.cli().getOption("hagroup"); - io.vertx.core.cli.Option quorumOption = executionContext.cli().getOption("quorum"); - if (!ha && - (commandLine.isOptionAssigned(haGroupOption) || commandLine.isOptionAssigned(quorumOption))) { - throw new CLIException("The option -hagroup and -quorum requires -ha to be enabled"); - } - } - - /** - * @return whether the {@code cluster} option or the {@code ha} option are enabled. Also {@code true} when a custom - * launcher modifies the Vert.x options to set `clustered` to {@code true} - */ - @Override - public boolean isClustered() { - return cluster || ha; - } - - @Override - public boolean getHA() { - return ha; - } - - /** - * Starts vert.x and deploy the verticle. - */ - @Override - public void run() { - if (redeploy == null || redeploy.isEmpty()) { - JsonObject conf = getConfiguration(); - if (conf == null) { - conf = new JsonObject(); - } - afterConfigParsed(conf); - - super.run(this::afterStoppingVertx); - if (vertx == null) { - // Already logged. - ExecUtils.exitBecauseOfVertxInitializationIssue(); - } - - if (vertx instanceof VertxInternal) { - ((VertxInternal) vertx).addCloseHook(this); - } - - deploymentOptions = new DeploymentOptions(); - configureFromSystemProperties(deploymentOptions, DEPLOYMENT_OPTIONS_PROP_PREFIX); - deploymentOptions.setConfig(conf).setWorker(worker).setHa(ha).setInstances(instances); - beforeDeployingVerticle(deploymentOptions); - deploy(); - } else { - // redeploy is set, start the redeployment infrastructure (watcher). - initializeRedeployment(); - } - } - - /** - * Initializes the redeployment cycle. In "redeploy mode", the application is launched as background, and is - * restarted after every change. A {@link Watcher} instance is responsible for monitoring files and triggering the - * redeployment. - */ - protected synchronized void initializeRedeployment() { - if (watcher != null) { - throw new IllegalStateException("Redeployment already started ? The watcher already exists"); - } - // Compute the application id. We append "-redeploy" to ease the identification in the process list. - vertxApplicationBackgroundId = UUID.randomUUID().toString() + "-redeploy"; - watcher = new Watcher(getCwd(), redeploy, - this::startAsBackgroundApplication, // On deploy - this::stopBackgroundApplication, // On undeploy - onRedeployCommand, // In between command - redeployGracePeriod, // The redeploy grace period - redeployScanPeriod); // The redeploy scan period - - // Close the watcher when the JVM is terminating. - // Notice that the vert.x finalizer is not registered when we run in redeploy mode. - Runtime.getRuntime().addShutdownHook(new Thread() { - public void run() { - shutdownRedeployment(); - } - }); - // Start the watching process, it triggers the initial deployment. - watcher.watch(); - } - - /** - * Stop the redeployment if started. - */ - protected synchronized void shutdownRedeployment() { - if (watcher != null) { - watcher.close(); - watcher = null; - } - } - - /** - * On-Undeploy action invoked while redeploying. It just stops the application launched in background. - * - * @param onCompletion an optional on-completion handler. If set it must be invoked at the end of this method. - */ - protected synchronized void stopBackgroundApplication(Handler onCompletion) { - executionContext.execute("stop", vertxApplicationBackgroundId, "--redeploy"); - if (redeployTerminationPeriod > 0) { - try { - Thread.sleep(redeployTerminationPeriod); - } catch (InterruptedException e) { - // Ignore the exception. - Thread.currentThread().interrupt(); - } - } - - if (onCompletion != null) { - onCompletion.handle(null); - } - } - - /** - * On-Deploy action invoked while redeploying. It just starts the application in background, copying all input - * parameters. In addition, the vertx application id is set. - * - * @param onCompletion an optional on-completion handler. If set it must be invoked at the end of this method. - */ - protected void startAsBackgroundApplication(Handler onCompletion) { - // We need to copy all options and arguments. - List args = new ArrayList<>(); - // Prepend the command. - args.add("run"); - args.add("--vertx-id=" + vertxApplicationBackgroundId); - args.addAll(executionContext.commandLine().allArguments()); - // No need to add the main-verticle as it's part of the allArguments list. - if (cluster) { - args.add("--cluster"); - } - if (clusterHost != null) { - args.add("--cluster-host=" + clusterHost); - } - if (clusterPort != 0) { - args.add("--cluster-port=" + clusterPort); - } - if (clusterPublicHost != null) { - args.add("--cluster-public-host=" + clusterPublicHost); - } - if (clusterPublicPort != -1) { - args.add("--cluster-public-port=" + clusterPublicPort); - } - if (ha) { - args.add("--ha"); - } - if (haGroup != null && !haGroup.equals("__DEFAULT__")) { - args.add("--hagroup=" + haGroup); - } - if (quorum != -1) { - args.add("--quorum=" + quorum); - } - if (classpath != null && !classpath.isEmpty()) { - args.add("--classpath=" + classpath.stream().collect(Collectors.joining(File.pathSeparator))); - } - if (vertxOptions != null) { - // Pass the configuration in 2 steps to quote correctly the options if it's an inlined json string - args.add("--options"); - args.add(vertxOptions); - } - if (config != null) { - // Pass the configuration in 2 steps to quote correctly the configuration if it's an inlined json string - args.add("--conf"); - args.add(config); - } - if (instances != 1) { - args.add("--instances=" + instances); - } - if (worker) { - args.add("--worker"); - } - if (systemProperties != null) { - args.addAll(systemProperties.stream().map(s -> "-D" + s).collect(Collectors.toList())); - } - - // Enable stream redirection - args.add("--redirect-output"); - - executionContext.execute("start", args.toArray(new String[0])); - if (onCompletion != null) { - onCompletion.handle(null); - } - } - - protected void deploy() { - deploy(mainVerticle, vertx, deploymentOptions, res -> { - if (res.failed()) { - handleDeployFailed(res.cause()); - } - }); - } - - private void handleDeployFailed(Throwable cause) { - if (executionContext.main() instanceof VertxLifecycleHooks) { - ((VertxLifecycleHooks) executionContext.main()).handleDeployFailed(vertx, mainVerticle, deploymentOptions, cause); - } else { - ExecUtils.exitBecauseOfVertxDeploymentIssue(); - } - } - - protected JsonObject getConfiguration() { - return getJsonFromFileOrString(config, "conf"); - } - - protected void beforeDeployingVerticle(DeploymentOptions deploymentOptions) { - final Object main = executionContext.main(); - if (main instanceof VertxLifecycleHooks) { - ((VertxLifecycleHooks) main).beforeDeployingVerticle(deploymentOptions); - } - } - - protected void afterConfigParsed(JsonObject config) { - final Object main = executionContext.main(); - if (main instanceof VertxLifecycleHooks) { - ((VertxLifecycleHooks) main).afterConfigParsed(config); - } - } - - protected void beforeStoppingVertx(Vertx vertx) { - final Object main = executionContext.main(); - if (main instanceof VertxLifecycleHooks) { - ((VertxLifecycleHooks) main).beforeStoppingVertx(vertx); - } - } - - protected void afterStoppingVertx() { - final Object main = executionContext.main(); - if (main instanceof VertxLifecycleHooks) { - ((VertxLifecycleHooks) main).afterStoppingVertx(); - } - } - - @Override - public void close(Promise completion) { - try { - beforeStoppingVertx(vertx); - completion.complete(); - } catch (Exception e) { - completion.fail(e); - } - } -} diff --git a/src/main/java/io/vertx/core/impl/launcher/commands/RunCommandFactory.java b/src/main/java/io/vertx/core/impl/launcher/commands/RunCommandFactory.java deleted file mode 100644 index 599e21e19ee..00000000000 --- a/src/main/java/io/vertx/core/impl/launcher/commands/RunCommandFactory.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl.launcher.commands; - -import io.vertx.core.spi.launcher.DefaultCommandFactory; - -/** - * Factory to create the {@code run} command. - * - * @author Clement Escoffier - */ -public class RunCommandFactory extends DefaultCommandFactory { - - /** - * Creates a new instance of {@link RunCommandFactory}. - */ - public RunCommandFactory() { - super(RunCommand.class, RunCommand::new); - } -} diff --git a/src/main/java/io/vertx/core/impl/launcher/commands/StartCommand.java b/src/main/java/io/vertx/core/impl/launcher/commands/StartCommand.java deleted file mode 100644 index 0b0e0b070b3..00000000000 --- a/src/main/java/io/vertx/core/impl/launcher/commands/StartCommand.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl.launcher.commands; - - -import io.vertx.core.cli.annotations.*; -import io.vertx.core.impl.launcher.CommandLineUtils; -import io.vertx.core.spi.launcher.DefaultCommand; - -import java.io.File; -import java.util.*; -import java.util.stream.Collectors; - -/** - * A command starting a vert.x application in the background. - * - * @author Clement Escoffier - */ -@Name("start") -@Summary("Start a vert.x application in background") -@Description("Start a vert.x application as a background service. The application is identified with an id that can be set using the `vertx-id` option. If not set a random UUID is generated. The application can be stopped with the `stop` command.") -public class StartCommand extends DefaultCommand { - - private String id; - private String launcher; - - private boolean redirect; - private String jvmOptions; - - /** - * Sets the "application id" that would be to stop the application and be lsited in the {@code list} command. - * - * @param id the id - */ - @Option(longName = "vertx-id", shortName = "id", required = false, acceptValue = true) - @Description("The id of the application, a random UUID by default") - public void setApplicationId(String id) { - this.id = id; - } - - /** - * Sets the Java Virtual Machine options to pass to the spawned process. If not set, the JAVA_OPTS environment - * variable is used. - * - * @param options the jvm options - */ - @Option(longName = "java-opts", required = false, acceptValue = true) - @Description("Java Virtual Machine options to pass to the spawned process such as \"-Xmx1G -Xms256m " + - "-XX:MaxPermSize=256m\". If not set the `JAVA_OPTS` environment variable is used.") - public void setJavaOptions(String options) { - this.jvmOptions = options; - } - - /** - * A hidden option to set the launcher class. - * - * @param clazz the class - */ - @Option(longName = "launcher-class") - @Hidden - public void setLauncherClass(String clazz) { - this.launcher = clazz; - } - - /** - * Whether or not the created process error streams and output streams needs to be redirected to the launcher process. - * - * @param redirect {@code true} to enable redirection, {@code false} otherwise - */ - @Option(longName = "redirect-output", flag = true) - @Hidden - public void setRedirect(boolean redirect) { - this.redirect = redirect; - } - - /** - * Starts the application in background. - */ - @Override - public void run() { - out.println("Starting vert.x application..."); - List cmd = new ArrayList<>(); - ProcessBuilder builder = new ProcessBuilder(); - addJavaCommand(cmd); - - // Must be called only once ! - List cliArguments = getArguments(); - - // Add the classpath to env. - builder.environment().put("CLASSPATH", System.getProperty("java.class.path")); - - if (launcher != null) { - ExecUtils.addArgument(cmd, launcher); - // Do we have a valid command ? - Optional maybeCommand = cliArguments.stream() - .filter(arg -> executionContext.launcher().getCommandNames().contains(arg)) - .findFirst(); - if (! maybeCommand.isPresent()) { - // No command, add `run` - ExecUtils.addArgument(cmd, "run"); - } - } else if (isLaunchedAsFatJar()) { - ExecUtils.addArgument(cmd, "-jar"); - ExecUtils.addArgument(cmd, CommandLineUtils.getJar()); - } else { - // probably a `vertx` command line usage, or in IDE. - ExecUtils.addArgument(cmd, CommandLineUtils.getFirstSegmentOfCommand()); - ExecUtils.addArgument(cmd, "run"); - } - - cliArguments.forEach(arg -> ExecUtils.addArgument(cmd, arg)); - - try { - builder.command(cmd); - if (redirect) { - builder.redirectError(ProcessBuilder.Redirect.INHERIT); - builder.redirectOutput(ProcessBuilder.Redirect.INHERIT); - } - builder.start(); - out.println(id); - } catch (Exception e) { - out.println("Cannot create vert.x application process"); - e.printStackTrace(out); - ExecUtils.exitBecauseOfProcessIssue(); - } - - } - - private void addJavaCommand(List cmd) { - if (ExecUtils.isWindows()) { - ExecUtils.addArgument(cmd, "cmd.exe"); - ExecUtils.addArgument(cmd, "/C"); - ExecUtils.addArgument(cmd, "start"); - ExecUtils.addArgument(cmd, "vertx-id - " + id); - ExecUtils.addArgument(cmd, "/B"); - } - ExecUtils.addArgument(cmd, getJava().getAbsolutePath()); - - // Compute JVM Options - if (jvmOptions == null) { - String opts = System.getenv("JAVA_OPTS"); - if (opts != null) { - Arrays.stream(opts.split(" ")).forEach(s -> ExecUtils.addArgument(cmd, s)); - } - } else { - Arrays.stream(jvmOptions.split(" ")).forEach(s -> ExecUtils.addArgument(cmd, s)); - } - } - - private File getJava() { - File java; - File home = new File(System.getProperty("java.home")); - if (ExecUtils.isWindows()) { - java = new File(home, "bin/java.exe"); - } else { - java = new File(home, "bin/java"); - } - - if (!java.isFile()) { - out.println("Cannot find java executable - " + java.getAbsolutePath() + " does not exist"); - ExecUtils.exitBecauseOfSystemConfigurationIssue(); - } - return java; - } - - private boolean isLaunchedAsFatJar() { - return CommandLineUtils.getJar() != null; - } - - private List getArguments() { - List args = executionContext.commandLine().allArguments(); - // Add system properties passed as parameter - if (systemProperties != null) { - systemProperties.stream().map(entry -> "-D" + entry).forEach(args::add); - } - - // Add id - it's important as it's the application mark. - args.add("-Dvertx.id=" + getId()); - return args; - } - - private String getId() { - if (id == null) { - id = UUID.randomUUID().toString(); - } - return id; - } -} diff --git a/src/main/java/io/vertx/core/impl/launcher/commands/StartCommandFactory.java b/src/main/java/io/vertx/core/impl/launcher/commands/StartCommandFactory.java deleted file mode 100644 index 16443e018ce..00000000000 --- a/src/main/java/io/vertx/core/impl/launcher/commands/StartCommandFactory.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl.launcher.commands; - -import io.vertx.core.spi.launcher.DefaultCommandFactory; - -/** - * Defines the `start` command. - * - * @author Clement Escoffier - */ -public class StartCommandFactory extends DefaultCommandFactory { - - /** - * Creates a new instance of {@link StartCommandFactory}. - */ - public StartCommandFactory() { - super(StartCommand.class, StartCommand::new); - } -} diff --git a/src/main/java/io/vertx/core/impl/launcher/commands/StopCommand.java b/src/main/java/io/vertx/core/impl/launcher/commands/StopCommand.java deleted file mode 100644 index a263e0cc7b9..00000000000 --- a/src/main/java/io/vertx/core/impl/launcher/commands/StopCommand.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl.launcher.commands; - -import io.vertx.core.cli.annotations.*; -import io.vertx.core.spi.launcher.DefaultCommand; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * A command stopping a vert.x application launched using the `start` command. The application is - * identified by its id. - * - * @author Clement Escoffier - */ -@Name("stop") -@Summary("Stop a vert.x application") -@Description("This command stops a vert.x application started with the `start` command. The command requires the " + - "application id as argument. Use the `list` command to get the list of applications") -public class StopCommand extends DefaultCommand { - - private String id; - - /** - * Whether or not we are in redeploy mode. In redeploy mode, do not exit the VM. - */ - private boolean redeploy; - - - private static final Pattern PS = Pattern.compile("([0-9]+)\\s.*-Dvertx.id=.*"); - - /** - * As the {@code stop} command takes only a single argument, it's the application id. - * - * @param id the id. - */ - @Argument(index = 0, argName = "vertx.id", required = false) - @Description("The vert.x application id") - public void setApplicationId(String id) { - this.id = id; - } - - @Option(longName = "redeploy", flag = true) - @Hidden - public void setRedeploy(boolean redeploy) { - this.redeploy = redeploy; - } - - /** - * Stops a running vert.x application launched with the `start` command. - */ - @Override - public void run() { - if (id == null) { - out.println("Application id not specified..."); - executionContext.execute("list"); - return; - } - - out.println("Stopping vert.x application '" + id + "'"); - if (ExecUtils.isWindows()) { - terminateWindowsApplication(); - } else { - terminateLinuxApplication(); - } - } - - private void terminateLinuxApplication() { - String pid = pid(); - if (pid == null) { - out.println("Cannot find process for application using the id '" + id + "'."); - if (!redeploy) { - ExecUtils.exitBecauseOfProcessIssue(); - } - return; - } - - List cmd = new ArrayList<>(); - cmd.add("kill"); - cmd.add(pid); - try { - int result = new ProcessBuilder(cmd).start().waitFor(); - out.println("Application '" + id + "' terminated with status " + result); - if (!redeploy) { - // We leave the application using the same exit code. - ExecUtils.exit(result); - } - } catch (Exception e) { - out.println("Failed to stop application '" + id + "'"); - e.printStackTrace(out); - if (!redeploy) { - ExecUtils.exitBecauseOfProcessIssue(); - } - } - } - - private void terminateWindowsApplication() { - String filter = "Name LIKE 'java%' AND CommandLine LIKE '%-Dvertx.id=" + id + "%'"; - String command = - "\"Get-CimInstance -ClassName Win32_Process -Filter \\\"" + filter + "\\\"" + - " | " + - "Invoke-CimMethod -MethodName Terminate\""; - List cmd = Arrays.asList("powershell", "-Command", command); - - try { - final Process process = new ProcessBuilder(cmd).start(); - int result = process.waitFor(); - out.println("Application '" + id + "' terminated with status " + result); - if (!redeploy) { - // We leave the application using the same exit code. - ExecUtils.exit(result); - } - } catch (Exception e) { - out.println("Failed to stop application '" + id + "'"); - e.printStackTrace(out); - if (!redeploy) { - ExecUtils.exitBecauseOfProcessIssue(); - } - } - } - - private String pid() { - try { - final Process process = new ProcessBuilder(Arrays.asList("sh", "-c", "ps ax | grep \"" + id + "\"")).start(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { - String line; - while ((line = reader.readLine()) != null) { - final Matcher matcher = PS.matcher(line); - if (matcher.find()) { - return matcher.group(1); - } - } - process.waitFor(); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - e.printStackTrace(out); - } catch (Exception e) { - e.printStackTrace(out); - } - return null; - } -} diff --git a/src/main/java/io/vertx/core/impl/launcher/commands/StopCommandFactory.java b/src/main/java/io/vertx/core/impl/launcher/commands/StopCommandFactory.java deleted file mode 100644 index 05faf740e57..00000000000 --- a/src/main/java/io/vertx/core/impl/launcher/commands/StopCommandFactory.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl.launcher.commands; - -import io.vertx.core.spi.launcher.DefaultCommandFactory; - -/** - * Defines the `stop` command. - * - * @author Clement Escoffier - */ -public class StopCommandFactory extends DefaultCommandFactory { - - /** - * Creates a new instance of {@link StopCommandFactory}. - */ - public StopCommandFactory() { - super(StopCommand.class, StopCommand::new); - } -} diff --git a/src/main/java/io/vertx/core/impl/launcher/commands/VersionCommand.java b/src/main/java/io/vertx/core/impl/launcher/commands/VersionCommand.java deleted file mode 100644 index b5dfecd741f..00000000000 --- a/src/main/java/io/vertx/core/impl/launcher/commands/VersionCommand.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl.launcher.commands; - -import io.vertx.core.cli.CLIException; -import io.vertx.core.cli.annotations.Description; -import io.vertx.core.cli.annotations.Name; -import io.vertx.core.cli.annotations.Summary; -import io.vertx.core.impl.logging.Logger; -import io.vertx.core.impl.logging.LoggerFactory; -import io.vertx.core.spi.launcher.DefaultCommand; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Scanner; - -/** - * Comment to display the vert.x (core) version. - * - * @author Clement Escoffier - */ -@Name("version") -@Summary("Displays the version.") -@Description("Prints the vert.x core version used by the application.") -public class VersionCommand extends DefaultCommand { - - private static final Logger log = LoggerFactory.getLogger(VersionCommand.class); - - private static String version; - - @Override - public void run() throws CLIException { - log.info(getVersion()); - } - - /** - * Reads the version from the {@code vertx-version.txt} file. - * - * @return the version - */ - public static String getVersion() { - if (version != null) { - return version; - } - try (InputStream is = VersionCommand.class.getClassLoader().getResourceAsStream("META-INF/vertx/vertx-version.txt")) { - if (is == null) { - throw new IllegalStateException("Cannot find vertx-version.txt on classpath"); - } - try (Scanner scanner = new Scanner(is, "UTF-8").useDelimiter("\\A")) { - return version = scanner.hasNext() ? scanner.next().trim() : ""; - } - } catch (IOException e) { - throw new IllegalStateException(e.getMessage()); - } - } -} diff --git a/src/main/java/io/vertx/core/impl/launcher/commands/VersionCommandFactory.java b/src/main/java/io/vertx/core/impl/launcher/commands/VersionCommandFactory.java deleted file mode 100644 index 4136f796ddf..00000000000 --- a/src/main/java/io/vertx/core/impl/launcher/commands/VersionCommandFactory.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl.launcher.commands; - -import io.vertx.core.spi.launcher.DefaultCommandFactory; - -/** - * Factory to create the {@code version} command. - * - * @author Clement Escoffier - */ -public class VersionCommandFactory extends DefaultCommandFactory { - - /** - * Creates a new instance of {@link VersionCommandFactory}. - */ - public VersionCommandFactory() { - super(VersionCommand.class, VersionCommand::new); - } -} diff --git a/src/main/java/io/vertx/core/impl/launcher/commands/VertxIsolatedDeployer.java b/src/main/java/io/vertx/core/impl/launcher/commands/VertxIsolatedDeployer.java deleted file mode 100644 index 6d79812e834..00000000000 --- a/src/main/java/io/vertx/core/impl/launcher/commands/VertxIsolatedDeployer.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl.launcher.commands; - -import io.vertx.core.*; -import io.vertx.core.impl.logging.Logger; -import io.vertx.core.impl.logging.LoggerFactory; - -/** - * A class isolating the deployment of verticle. - * - * @author Clement Escoffier - */ -public class VertxIsolatedDeployer { - - private static final Logger log = LoggerFactory.getLogger(VertxIsolatedDeployer.class); - - private String deploymentId; - private Vertx vertx; - - /** - * Deploys the given verticle. - * - * @param verticle the verticle name - * @param vertx the vert.x instance - * @param options the deployment options - * @param completionHandler the completion handler - */ - public void deploy(String verticle, Vertx vertx, DeploymentOptions options, - Handler> completionHandler) { - this.vertx = vertx; - String message = (options.isWorker()) ? "deploying worker verticle" : "deploying verticle"; - vertx.deployVerticle(verticle, options).onComplete(createHandler(message, completionHandler)); - } - - /** - * Undeploys the previously deployed verticle. - * - * @param completionHandler the completion handler - */ - public void undeploy(Handler> completionHandler) { - vertx.undeploy(deploymentId).onComplete(res -> { - if (res.failed()) { - log.error("Failed in undeploying " + deploymentId, res.cause()); - } else { - log.info("Succeeded in undeploying " + deploymentId); - } - deploymentId = null; - completionHandler.handle(res); - }); - } - - - private Handler> createHandler(final String message, - final Handler> - completionHandler) { - return res -> { - if (res.failed()) { - Throwable cause = res.cause(); - if (cause instanceof VertxException) { - VertxException ve = (VertxException) cause; - if (ve.getCause() == null) { - log.error(ve.getMessage()); - } else { - log.error(ve.getMessage(), ve.getCause()); - } - } else { - log.error("Failed in " + message, cause); - } - } else { - deploymentId = res.result(); - log.info("Succeeded in " + message); - } - if (completionHandler != null) { - completionHandler.handle(res); - } - }; - } -} diff --git a/src/main/java/io/vertx/core/impl/launcher/commands/Watcher.java b/src/main/java/io/vertx/core/impl/launcher/commands/Watcher.java deleted file mode 100644 index e70a16aac3d..00000000000 --- a/src/main/java/io/vertx/core/impl/launcher/commands/Watcher.java +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.impl.launcher.commands; - -import io.vertx.core.Handler; -import io.vertx.core.impl.logging.Logger; -import io.vertx.core.impl.logging.LoggerFactory; - -import java.io.File; -import java.nio.file.WatchService; -import java.util.*; -import java.util.stream.Collectors; - -/** - * A file alteration monitor based on a home made file system scan and watching files matching a set of includes - * patterns. These patterns are Ant patterns (can use {@literal **, * or ?}). This class takes 2 {@link Handler} as - * parameter and orchestrate the redeployment method when a matching file is modified (created, updated or deleted). - * Users have the possibility to execute a shell command during the redeployment. On a file change, the {@code undeploy} - * {@link Handler} is called, followed by the execution of the user command. Then the {@code deploy} {@link Handler} - * is invoked. - *

- * The watcher watches all files from the current directory and sub-directories. - * - * @author Clement Escoffier - */ -public class Watcher implements Runnable { - - private final static Logger LOGGER = LoggerFactory.getLogger(Watcher.class); - - private final long gracePeriod; - private final Map> fileMap = new LinkedHashMap<>(); - private final Set filesToWatch = new HashSet<>(); - private final long scanPeriod; - private final List roots; - private final File cwd; - - /** - * This field is always access from the scan thread. No need to be volatile. - */ - private long lastChange = -1; - - private final List includes; - private final Handler> deploy; - private final Handler> undeploy; - private final String cmd; - - private volatile boolean closed; - - /** - * Creates a new {@link Watcher}. - * - * @param root the root directory - * @param includes the list of include patterns, should not be {@code null} or empty - * @param deploy the function called when deployment is required - * @param undeploy the function called when un-deployment is required - * @param onRedeployCommand an optional command executed after the un-deployment and before the deployment - * @param gracePeriod the amount of time in milliseconds to wait between two redeploy even - * if there are changes - * @param scanPeriod the time in millisecond between 2 file system scans - */ - public Watcher(File root, List includes, Handler> deploy, Handler> undeploy, - String onRedeployCommand, long gracePeriod, long scanPeriod) { - this.gracePeriod = gracePeriod; - this.includes = sanitizeIncludePatterns(includes); - this.roots = extractRoots(root, this.includes); - this.cwd = root; - LOGGER.info("Watched paths: " + this.roots); - this.deploy = deploy; - this.undeploy = undeploy; - this.cmd = onRedeployCommand; - this.scanPeriod = scanPeriod; - addFilesToWatchedList(roots); - } - - static List extractRoots(File root, List includes) { - return includes.stream().map(s -> { - if (s.startsWith("*")) { - return root.getAbsolutePath(); - } - if (s.contains("*")) { - s = s.substring(0, s.indexOf("*")); - } - File file = new File(s); - if (file.isAbsolute()) { - return file.getAbsolutePath(); - } else { - return new File(root, s).getAbsolutePath(); - } - }).collect(Collectors.toSet()).stream().map(File::new).collect(Collectors.toList()); - } - - private List sanitizeIncludePatterns(List includes) { - return includes.stream().map(p -> { - if (ExecUtils.isWindows()) { - return p.replace('/', File.separatorChar); - } - return p.replace('\\', File.separatorChar); - }).collect(Collectors.toList()); - } - - private void addFilesToWatchedList(List roots) { - roots.forEach(this::addFileToWatchedList); - } - - private void addFileToWatchedList(File file) { - filesToWatch.add(file); - Map map = new HashMap<>(); - if (file.isDirectory()) { - // We're watching a directory contents and its children for changes - File[] children = file.listFiles(); - if (children != null) { - for (File child : children) { - map.put(child, new FileInfo(child.lastModified(), child.length())); - if (child.isDirectory()) { - addFileToWatchedList(child); - } - } - } - } else { - // Not a directory - we're watching a specific file - e.g. a jar - map.put(file, new FileInfo(file.lastModified(), file.length())); - } - fileMap.put(file, map); - } - - /** - * Checks whether or not a change has occurred in one of the watched file that match one of the given include pattern - * . Are detected: new files, modified file and deleted files. File modification is detected using - * {@link File#lastModified()}, so the behavior depends on the file system precision. - * - * @return {@code true} if a change occurred requiring the redeployment. - */ - private boolean changesHaveOccurred() { - boolean changed = false; - for (File toWatch : new HashSet<>(filesToWatch)) { - - // The new files in the directory - Map newFiles = new LinkedHashMap<>(); - if (toWatch.isDirectory()) { - File[] files = toWatch.exists() ? toWatch.listFiles() : new File[]{}; - - if (files == null) { - // something really bad happened to the file system. - throw new IllegalStateException("Cannot scan the file system to detect file changes"); - } - - for (File file : files) { - newFiles.put(file, file); - } - } else { - newFiles.put(toWatch, toWatch); - } - - // Lookup the old list for that file/directory - Map currentFileMap = fileMap.get(toWatch); - for (Map.Entry currentEntry : new HashMap<>(currentFileMap).entrySet()) { - File currFile = currentEntry.getKey(); - FileInfo currInfo = currentEntry.getValue(); - File newFile = newFiles.get(currFile); - if (newFile == null) { - // File has been deleted - currentFileMap.remove(currFile); - if (currentFileMap.isEmpty()) { - fileMap.remove(toWatch); - filesToWatch.remove(toWatch); - } - LOGGER.trace("File: " + currFile + " has been deleted"); - if (match(currFile)) { - changed = true; - } - } else if (newFile.lastModified() != currInfo.lastModified || newFile.length() != currInfo.length) { - // File has been modified - currentFileMap.put(newFile, new FileInfo(newFile.lastModified(), newFile.length())); - LOGGER.trace("File: " + currFile + " has been modified"); - if (match(currFile)) { - changed = true; - } - } - } - - // Now process any added files - for (File newFile : newFiles.keySet()) { - if (!currentFileMap.containsKey(newFile)) { - // Add new file - currentFileMap.put(newFile, new FileInfo(newFile.lastModified(), newFile.length())); - if (newFile.isDirectory()) { - addFileToWatchedList(newFile); - } - LOGGER.trace("File was added: " + newFile); - if (match(newFile)) { - changed = true; - } - } - } - } - - long now = System.currentTimeMillis(); - if (changed) { - lastChange = now; - } - - if (lastChange != -1 && now - lastChange >= gracePeriod) { - lastChange = -1; - return true; - } - - return false; - } - - - /** - * Checks whether the given file matches one of the {@link #includes} patterns. - * - * @param file the file - * @return {@code true} if the file matches at least one pattern, {@code false} otherwise. - */ - protected boolean match(File file) { - // Compute relative path. - String rel = null; - String relFromCwd = null; - for (File root : roots) { - if (file.getAbsolutePath().startsWith(root.getAbsolutePath())) { - if (file.getAbsolutePath().equals(root.getAbsolutePath())) { - rel = file.getAbsolutePath(); - } else { - rel = file.getAbsolutePath().substring(root.getAbsolutePath().length() + 1); - } - } - } - if (rel == null) { - LOGGER.warn("A change in " + file.getAbsolutePath() + " has been detected, but the file does not belong to a " + - "watched roots: " + roots); - return false; - } - - if (file.getAbsolutePath().startsWith(cwd.getAbsolutePath())) { - relFromCwd = file.getAbsolutePath().substring(cwd.getAbsolutePath().length() + 1); - } - - for (String include : includes) { - // Windows files are not case sensitive - // 3 checks: two for the relative file (root and cwd), and one taking the absolute path, for pattern using - // absolute path - if ((relFromCwd != null && FileSelector.matchPath(include, relFromCwd, !ExecUtils.isWindows())) - || FileSelector.matchPath(include, file.getAbsolutePath(), !ExecUtils.isWindows())) { - return true; - } - } - return false; - } - - /** - * Starts watching. The watch processing is made in another thread started in this method. - * - * @return the current watcher. - */ - public Watcher watch() { - new Thread(this).start(); - LOGGER.info("Starting the vert.x application in redeploy mode"); - deploy.handle(null); - - return this; - } - - /** - * Stops watching. This method stops the underlying {@link WatchService}. - */ - public void close() { - LOGGER.info("Stopping redeployment"); - // closing the redeployment thread. If waiting, it will shutdown at the next iteration. - closed = true; - // Un-deploy application on close. - undeploy.handle(null); - } - - /** - * The watching thread runnable method. - */ - @Override - public void run() { - try { - while (!closed) { - if (changesHaveOccurred()) { - trigger(); - } - // Wait for the next scan. - Thread.sleep(scanPeriod); - } - } catch (Throwable e) { - LOGGER.error("An error have been encountered while watching resources - leaving the redeploy mode", e); - close(); - } - } - - /** - * Redeployment process. - */ - private void trigger() { - long begin = System.currentTimeMillis(); - LOGGER.info("Redeploying!"); - // 1) - undeploy.handle(v1 -> { - // 2) - executeUserCommand(v2 -> { - // 3) - deploy.handle(v3 -> { - long end = System.currentTimeMillis(); - LOGGER.info("Redeployment done in " + (end - begin) + " ms."); - }); - }); - }); - } - - private void executeUserCommand(Handler onCompletion) { - if (cmd != null) { - try { - List command = new ArrayList<>(); - if (ExecUtils.isWindows()) { - ExecUtils.addArgument(command, "cmd"); - ExecUtils.addArgument(command, "/c"); - } else { - ExecUtils.addArgument(command, "sh"); - ExecUtils.addArgument(command, "-c"); - } - // Do not add quote to the given command: - command.add(cmd); - - final Process process = new ProcessBuilder(command) - .redirectError(ProcessBuilder.Redirect.INHERIT) - .redirectOutput(ProcessBuilder.Redirect.INHERIT) - .start(); - - int status = process.waitFor(); - LOGGER.info("User command terminated with status " + status); - } catch (Throwable e) { - LOGGER.error("Error while executing the on-redeploy command : '" + cmd + "'", e); - } - } - onCompletion.handle(null); - } - - private static final class FileInfo { - long lastModified; - long length; - - private FileInfo(long lastModified, long length) { - this.lastModified = lastModified; - this.length = length; - } - } -} diff --git a/src/main/java/io/vertx/core/impl/logging/LoggerAdapter.java b/src/main/java/io/vertx/core/impl/logging/LoggerAdapter.java index 5f0d2ab32cc..88b12e7508e 100644 --- a/src/main/java/io/vertx/core/impl/logging/LoggerAdapter.java +++ b/src/main/java/io/vertx/core/impl/logging/LoggerAdapter.java @@ -20,7 +20,8 @@ public final class LoggerAdapter implements Logger { private final LogDelegate adapted; - LoggerAdapter(LogDelegate adapted) { + // Visible for testing + public LoggerAdapter(LogDelegate adapted) { this.adapted = adapted; } @@ -93,4 +94,8 @@ public void error(Object message) { public void error(Object message, Throwable t) { adapted.error(message, t); } + + public LogDelegate unwrap() { + return adapted; + } } diff --git a/src/main/java/io/vertx/core/impl/logging/LoggerFactory.java b/src/main/java/io/vertx/core/impl/logging/LoggerFactory.java index 5ba32154fca..03d49530041 100644 --- a/src/main/java/io/vertx/core/impl/logging/LoggerFactory.java +++ b/src/main/java/io/vertx/core/impl/logging/LoggerFactory.java @@ -11,25 +11,77 @@ package io.vertx.core.impl.logging; +import io.vertx.core.logging.JULLogDelegateFactory; +import io.vertx.core.spi.logging.LogDelegate; +import io.vertx.core.spi.logging.LogDelegateFactory; + /** * For internal logging purposes only. * * @author Thomas Segismont */ -@SuppressWarnings("deprecation") public class LoggerFactory { + public static final String LOGGER_DELEGATE_FACTORY_CLASS_NAME = "vertx.logger-delegate-factory-class-name"; + + private static volatile LogDelegateFactory delegateFactory; + + static { + initialise(); + // Do not log before being fully initialized (a logger extension may use Vert.x classes) + LogDelegate log = delegateFactory.createDelegate(LoggerFactory.class.getName()); + log.debug("Using " + delegateFactory.getClass().getName()); + } + + private static synchronized void initialise() { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + String className; + try { + className = System.getProperty(LOGGER_DELEGATE_FACTORY_CLASS_NAME); + } catch (Exception ignore) { + className = null; + } + if (className != null && configureWith(className, false, loader)) { + return; + } + if (loader.getResource("vertx-default-jul-logging.properties") == null) { + if (configureWith("SLF4J", true, loader) + || configureWith("Log4j2", true, loader)) { + return; + } + } + // Do not use dynamic classloading here to ensure the class is visible by AOT compilers + delegateFactory = new JULLogDelegateFactory(); + } + + private static boolean configureWith(String name, boolean shortName, ClassLoader loader) { + try { + Class clazz = Class.forName(shortName ? "io.vertx.core.logging." + name + "LogDelegateFactory" : name, true, loader); + LogDelegateFactory factory = (LogDelegateFactory) clazz.getDeclaredConstructor().newInstance(); + if (!factory.isAvailable()) { + return false; + } + delegateFactory = factory; + return true; + } catch (Throwable ignore) { + return false; + } + } + /** * Like {@link #getLogger(String)}, using the provided {@code clazz} name. */ public static Logger getLogger(Class clazz) { - return new LoggerAdapter(io.vertx.core.logging.LoggerFactory.getLogger(clazz).getDelegate()); + String name = clazz.isAnonymousClass() ? + clazz.getEnclosingClass().getCanonicalName() : + clazz.getCanonicalName(); + return getLogger(name); } /** * Get the logger with the specified {@code name}. */ public static Logger getLogger(String name) { - return new LoggerAdapter(io.vertx.core.logging.LoggerFactory.getLogger(name).getDelegate()); + return new LoggerAdapter(delegateFactory.createDelegate(name)); } } diff --git a/src/main/java/io/vertx/core/impl/transports/EpollTransport.java b/src/main/java/io/vertx/core/impl/transports/EpollTransport.java index 0d878691f57..821cb037b92 100644 --- a/src/main/java/io/vertx/core/impl/transports/EpollTransport.java +++ b/src/main/java/io/vertx/core/impl/transports/EpollTransport.java @@ -150,7 +150,7 @@ public void configure(NetServerOptions options, boolean domainSocket, ServerBoot } @Override - public void configure(ClientOptionsBase options, boolean domainSocket, Bootstrap bootstrap) { + public void configure(ClientOptionsBase options, int connectTimeout, boolean domainSocket, Bootstrap bootstrap) { if (!domainSocket) { if (options.isTcpFastOpen()) { bootstrap.option(ChannelOption.TCP_FASTOPEN_CONNECT, options.isTcpFastOpen()); @@ -159,6 +159,6 @@ public void configure(ClientOptionsBase options, boolean domainSocket, Bootstrap bootstrap.option(EpollChannelOption.TCP_QUICKACK, options.isTcpQuickAck()); bootstrap.option(EpollChannelOption.TCP_CORK, options.isTcpCork()); } - Transport.super.configure(options, domainSocket, bootstrap); + Transport.super.configure(options, connectTimeout, domainSocket, bootstrap); } } diff --git a/src/main/java/io/vertx/core/json/jackson/DatabindCodec.java b/src/main/java/io/vertx/core/json/jackson/DatabindCodec.java index 46014ab07b4..59cfa1f7240 100644 --- a/src/main/java/io/vertx/core/json/jackson/DatabindCodec.java +++ b/src/main/java/io/vertx/core/json/jackson/DatabindCodec.java @@ -15,10 +15,9 @@ import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.module.SimpleModule; import io.netty.buffer.ByteBufInputStream; import io.vertx.core.buffer.Buffer; +import io.vertx.core.buffer.impl.BufferInternal; import io.vertx.core.json.DecodeException; import io.vertx.core.json.EncodeException; import io.vertx.core.json.JsonArray; @@ -26,7 +25,6 @@ import java.io.IOException; import java.io.InputStream; -import java.time.Instant; import java.util.List; import java.util.Map; @@ -36,7 +34,6 @@ public class DatabindCodec extends JacksonCodec { private static final ObjectMapper mapper = new ObjectMapper(); - private static final ObjectMapper prettyMapper = new ObjectMapper(); static { initialize(); @@ -46,12 +43,8 @@ private static void initialize() { // Non-standard JSON but we allow C style comments in our JSON mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true); - prettyMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true); - prettyMapper.configure(SerializationFeature.INDENT_OUTPUT, true); - VertxModule module = new VertxModule(); mapper.registerModule(module); - prettyMapper.registerModule(module); } /** @@ -61,13 +54,6 @@ public static ObjectMapper mapper() { return mapper; } - /** - * @return the {@link ObjectMapper} used for data binding configured for indenting output. - */ - public static ObjectMapper prettyMapper() { - return prettyMapper; - } - @Override public T fromValue(Object json, Class clazz) { T value = DatabindCodec.mapper.convertValue(json, clazz); @@ -103,7 +89,7 @@ public T fromBuffer(Buffer buf, TypeReference typeRef) throws DecodeExcep return fromParser(createParser(buf), typeRef); } - public static JsonParser createParser(Buffer buf) { + public static JsonParser createParser(BufferInternal buf) { try { return DatabindCodec.mapper.getFactory().createParser((InputStream) new ByteBufInputStream(buf.getByteBuf())); } catch (IOException e) { @@ -157,8 +143,13 @@ private static T fromParser(JsonParser parser, TypeReference type) throws @Override public String toString(Object object, boolean pretty) throws EncodeException { try { - ObjectMapper mapper = pretty ? DatabindCodec.prettyMapper : DatabindCodec.mapper; - return mapper.writeValueAsString(object); + String result; + if (pretty) { + result = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(object); + } else { + result = mapper.writeValueAsString(object); + } + return result; } catch (Exception e) { throw new EncodeException("Failed to encode as JSON: " + e.getMessage()); } @@ -167,8 +158,13 @@ public String toString(Object object, boolean pretty) throws EncodeException { @Override public Buffer toBuffer(Object object, boolean pretty) throws EncodeException { try { - ObjectMapper mapper = pretty ? DatabindCodec.prettyMapper : DatabindCodec.mapper; - return Buffer.buffer(mapper.writeValueAsBytes(object)); + byte[] result; + if (pretty) { + result = mapper.writerWithDefaultPrettyPrinter().writeValueAsBytes(object); + } else { + result = mapper.writeValueAsBytes(object); + } + return Buffer.buffer(result); } catch (Exception e) { throw new EncodeException("Failed to encode as JSON: " + e.getMessage()); } diff --git a/src/main/java/io/vertx/core/json/jackson/HybridJacksonPool.java b/src/main/java/io/vertx/core/json/jackson/HybridJacksonPool.java new file mode 100644 index 00000000000..0b859cee379 --- /dev/null +++ b/src/main/java/io/vertx/core/json/jackson/HybridJacksonPool.java @@ -0,0 +1,251 @@ +package io.vertx.core.json.jackson; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.function.Predicate; +import java.util.stream.IntStream; + +import com.fasterxml.jackson.core.util.BufferRecycler; +import com.fasterxml.jackson.core.util.JsonRecyclerPools; +import com.fasterxml.jackson.core.util.RecyclerPool; + +/** + * This is a custom implementation of the Jackson's {@link RecyclerPool} intended to work equally well with both + * platform and virtual threads. This pool works regardless of the version of the JVM in use and internally uses + * 2 distinct pools one for platform threads (which is exactly the same {@link ThreadLocal} based one provided + * by Jackson out of the box) and the other designed for being virtual threads friendly. It switches between + * the 2 only depending on the nature of thread (virtual or not) requiring the acquisition of a pooled resource, + * obtained via {@link MethodHandle} to guarantee compatibility also with old JVM versions. The pool also guarantees + * that the pooled resource is always released to the same internal pool from where it has been acquired, regardless + * if the releasing thread is different from the one that originally made the acquisition. + *

+ * The virtual thread friendly inner pool is implemented with N striped linked lists using a simple lock free + * algorithm based on CAS. The striping is performed shuffling the id of the thread requiring to acquire a pooled + * resource with a xorshift based computation. The resulting of this computation is also stored in the pooled resource, + * bringing the twofold advantage of always releasing the resource in the same bucket from where it has been taken + * regardless if the releasing thread is different from the one that did the acquisition and avoiding the need of + * recalculating the position of that bucket also during the release. The heads of the linked lists are hold in an + * {@link AtomicReferenceArray} where each head has a distance of 16 positions from the adjacent ones to prevent + * the false sharing problem. + */ +public class HybridJacksonPool implements RecyclerPool { + + private static final HybridJacksonPool INSTANCE = new HybridJacksonPool(); + + private static final Predicate isVirtual = VirtualPredicate.findIsVirtualPredicate(); + + private final RecyclerPool nativePool = JsonRecyclerPools.threadLocalPool(); + + private static class VirtualPoolHolder { + // Lazy on-demand initialization + private static final StripedLockFreePool virtualPool = new StripedLockFreePool(Runtime.getRuntime().availableProcessors()); + } + + private HybridJacksonPool() { + // prevent external instantiation + } + + public static HybridJacksonPool getInstance() { + return INSTANCE; + } + + @Override + public BufferRecycler acquirePooled() { + return isVirtual.test(Thread.currentThread()) ? + VirtualPoolHolder.virtualPool.acquirePooled() : + nativePool.acquirePooled(); + } + + @Override + public BufferRecycler acquireAndLinkPooled() { + // when using the ThreadLocal based pool it is not necessary to register the BufferRecycler on the pool + return isVirtual.test(Thread.currentThread()) ? + VirtualPoolHolder.virtualPool.acquireAndLinkPooled() : + nativePool.acquirePooled(); + } + + @Override + public void releasePooled(BufferRecycler bufferRecycler) { + if (bufferRecycler instanceof VThreadBufferRecycler) { + // if it is a PooledBufferRecycler it has been acquired by a virtual thread, so it has to be release to the same pool + VirtualPoolHolder.virtualPool.releasePooled(bufferRecycler); + } + // the native thread pool is based on ThreadLocal, so it doesn't have anything to do on release + } + + static class StripedLockFreePool implements RecyclerPool { + + private static final int CACHE_LINE_SHIFT = 4; + + private static final int CACHE_LINE_PADDING = 1 << CACHE_LINE_SHIFT; + + private final XorShiftThreadProbe threadProbe; + + private final AtomicReferenceArray topStacks; + + private final int stripesCount; + + public StripedLockFreePool(int stripesCount) { + if (stripesCount <= 0) { + throw new IllegalArgumentException("Expecting a stripesCount that is larger than 0"); + } + + this.stripesCount = stripesCount; + int size = roundToPowerOfTwo(stripesCount); + this.topStacks = new AtomicReferenceArray<>(size * CACHE_LINE_PADDING); + + int mask = (size - 1) << CACHE_LINE_SHIFT; + this.threadProbe = new XorShiftThreadProbe(mask); + } + + public int size() { + return stackSizes().sum(); + } + + public int[] stackStats() { + return stackSizes().toArray(); + } + + private IntStream stackSizes() { + return IntStream.range(0, stripesCount).map(i -> { + Node node = topStacks.get(i * CACHE_LINE_PADDING); + return node == null ? 0 : node.level; + }); + } + + @Override + public BufferRecycler acquirePooled() { + int index = threadProbe.index(); + + Node currentHead = topStacks.get(index); + while (true) { + if (currentHead == null) { + return new VThreadBufferRecycler(index); + } + + if (topStacks.compareAndSet(index, currentHead, currentHead.next)) { + currentHead.next = null; + return currentHead.value; + } else { + currentHead = topStacks.get(index); + } + } + } + + @Override + public void releasePooled(BufferRecycler recycler) { + VThreadBufferRecycler vThreadBufferRecycler = (VThreadBufferRecycler) recycler; + Node newHead = new Node(vThreadBufferRecycler); + + Node next = topStacks.get(vThreadBufferRecycler.slot); + while (true) { + newHead.level = next == null ? 1 : next.level + 1; + if (topStacks.compareAndSet(vThreadBufferRecycler.slot, next, newHead)) { + newHead.next = next; + return; + } else { + next = topStacks.get(vThreadBufferRecycler.slot); + } + } + } + + private static class Node { + final VThreadBufferRecycler value; + Node next; + int level = 0; + + Node(VThreadBufferRecycler value) { + this.value = value; + } + } + } + + private static class VThreadBufferRecycler extends BufferRecycler { + private final int slot; + + VThreadBufferRecycler(int slot) { + this.slot = slot; + } + } + + private static class VirtualPredicate { + private static final MethodHandle virtualMh = findVirtualMH(); + + private static MethodHandle findVirtualMH() { + try { + return MethodHandles.publicLookup().findVirtual(Thread.class, "isVirtual", + MethodType.methodType(boolean.class)); + } catch (Exception e) { + return null; + } + } + + private static Predicate findIsVirtualPredicate() { + if (virtualMh != null) { + return new Predicate() { + @Override + public boolean test(Thread thread) { + try { + return (boolean) virtualMh.invokeExact(thread); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + }; + } + + return new Predicate() { + @Override + public boolean test(Thread thread) { + return false; + } + }; + } + } + + /** + * This class is used to hash the thread requiring a pooled resource using a multiplicative + * Fibonacci hashing implementation. The resulting hash is then used to calculate the + * index of the bucket in the pool from where the pooled resource has to be retrieved. + */ + private static class XorShiftThreadProbe { + + private final int mask; + + XorShiftThreadProbe(int mask) { + this.mask = mask; + } + + public int index() { + return probe() & mask; + } + + private int probe() { + // Multiplicative Fibonacci hashing implementation + // 0x9e3779b9 is the integral part of the Golden Ratio's fractional part 0.61803398875… (sqrt(5)-1)/2 + // multiplied by 2^32, which has the best possible scattering properties. + int probe = (int) ((Thread.currentThread().getId() * 0x9e3779b9) & Integer.MAX_VALUE); + // xorshift + probe ^= probe << 13; + probe ^= probe >>> 17; + probe ^= probe << 5; + return probe; + } + } + + private static final int MAX_POW2 = 1 << 30; + + private static int roundToPowerOfTwo(final int value) { + if (value > MAX_POW2) { + throw new IllegalArgumentException( + "There is no larger power of 2 int for value:" + value + " since it exceeds 2^31."); + } + if (value < 0) { + throw new IllegalArgumentException("Given value:" + value + ". Expecting value >= 0."); + } + final int nextPow2 = 1 << (32 - Integer.numberOfLeadingZeros(value - 1)); + return nextPow2; + } +} diff --git a/src/main/java/io/vertx/core/json/jackson/JacksonCodec.java b/src/main/java/io/vertx/core/json/jackson/JacksonCodec.java index 935ee591532..0d7308068e5 100644 --- a/src/main/java/io/vertx/core/json/jackson/JacksonCodec.java +++ b/src/main/java/io/vertx/core/json/jackson/JacksonCodec.java @@ -11,17 +11,14 @@ package io.vertx.core.json.jackson; -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonToken; -import com.fasterxml.jackson.core.JsonTokenId; +import com.fasterxml.jackson.core.*; import com.fasterxml.jackson.core.type.TypeReference; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufInputStream; import io.netty.buffer.ByteBufOutputStream; -import io.netty.buffer.Unpooled; import io.vertx.core.buffer.Buffer; +import io.vertx.core.buffer.impl.BufferInternal; +import io.vertx.core.buffer.impl.VertxByteBufAllocator; import io.vertx.core.json.DecodeException; import io.vertx.core.json.EncodeException; import io.vertx.core.json.JsonArray; @@ -34,6 +31,7 @@ import java.io.OutputStream; import java.io.StringWriter; import java.io.Writer; +import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.math.BigDecimal; @@ -53,11 +51,31 @@ */ public class JacksonCodec implements JsonCodec { - private static final JsonFactory factory = new JsonFactory(); + private static JsonFactory buildFactory() { + TSFBuilder builder = JsonFactory.builder(); + try { + // Use reflection to configure the recycler pool + Method[] methods = builder.getClass().getMethods(); + for (Method method : methods) { + if (method.getName().equals("recyclerPool")) { + Class poolClass = JacksonCodec.class.getClassLoader().loadClass("io.vertx.core.json.jackson.HybridJacksonPool"); + Method getInstanceMethod = poolClass.getMethod("getInstance"); + Object pool = getInstanceMethod.invoke(null); + method.invoke(builder, pool); + break; + } + } + } catch (Throwable e) { + // Ignore: most likely no Recycler Pool with Jackson < 2.16 + } + return builder.build(); + } + + private static final JsonFactory factory = buildFactory(); static { // Non-standard JSON but we allow C style comments in our JSON - factory.configure(JsonParser.Feature.ALLOW_COMMENTS, true); + JacksonCodec.factory.configure(JsonParser.Feature.ALLOW_COMMENTS, true); } @Override @@ -104,16 +122,19 @@ public String toString(Object object, boolean pretty) throws EncodeException { @Override public Buffer toBuffer(Object object, boolean pretty) throws EncodeException { - ByteBuf buf = Unpooled.buffer(); + // these buffers are not pooled and not instrumented, which make them faster + // This can use 0 as initial capacity, because Jackson will flush the tmp encoded buffer in one go, on flush + // causing a single resize of the right final size + ByteBuf nettyBuffer = VertxByteBufAllocator.DEFAULT.heapBuffer(0, Integer.MAX_VALUE); // There is no need to use a try with resources here as jackson // is a well-behaved and always calls the closes all streams in the // "finally" block bellow. - ByteBufOutputStream out = new ByteBufOutputStream(buf); + ByteBufOutputStream out = new ByteBufOutputStream(nettyBuffer); JsonGenerator generator = createGenerator(out, pretty); try { encodeJson(object, generator); generator.flush(); - return Buffer.buffer(buf); + return BufferInternal.buffer(nettyBuffer); } catch (IOException e) { throw new EncodeException(e.getMessage(), e); } finally { @@ -131,7 +152,7 @@ public static JsonParser createParser(String str) { public static JsonParser createParser(Buffer buf) { try { - return factory.createParser((InputStream) new ByteBufInputStream(buf.getByteBuf())); + return factory.createParser((InputStream) new ByteBufInputStream(((BufferInternal)buf).getByteBuf())); } catch (IOException e) { throw new DecodeException("Failed to decode:" + e.getMessage(), e); } @@ -278,54 +299,50 @@ private static void encodeJson(Object json, JsonGenerator generator) throws Enco generator.writeStartObject(); for (Map.Entry e : ((Map)json).entrySet()) { generator.writeFieldName(e.getKey()); - encodeJson(e.getValue(), generator); + Object value = e.getValue(); + encodeJson0(value, generator); } generator.writeEndObject(); } else if (json instanceof List) { generator.writeStartArray(); for (Object item : (List) json) { - encodeJson(item, generator); + encodeJson0(item, generator); } generator.writeEndArray(); - } else if (json instanceof String) { - generator.writeString((String) json); - } else if (json instanceof Number) { - if (json instanceof Short) { - generator.writeNumber((Short) json); - } else if (json instanceof Integer) { - generator.writeNumber((Integer) json); - } else if (json instanceof Long) { - generator.writeNumber((Long) json); - } else if (json instanceof Float) { - generator.writeNumber((Float) json); - } else if (json instanceof Double) { - generator.writeNumber((Double) json); - } else if (json instanceof Byte) { - generator.writeNumber((Byte) json); - } else if (json instanceof BigInteger) { - generator.writeNumber((BigInteger) json); - } else if (json instanceof BigDecimal) { - generator.writeNumber((BigDecimal) json); - } else { - generator.writeNumber(((Number) json).doubleValue()); + } else if (!encodeSingleType(generator, json)) { + throw new EncodeException("Mapping " + json.getClass().getName() + " is not available without Jackson Databind on the classpath"); + } + } catch (IOException e) { + throw new EncodeException(e.getMessage(), e); + } + } + + /** + * This is a way to overcome a limit of OpenJDK on MaxRecursiveInlineLevel: + * avoiding the "direct" recursive calls allow the JIT to have a better inlining budget for the recursive calls. + */ + private static void encodeJson0(Object json, JsonGenerator generator) throws EncodeException { + try { + if (json instanceof JsonObject) { + json = ((JsonObject)json).getMap(); + } else if (json instanceof JsonArray) { + json = ((JsonArray)json).getList(); + } + if (json instanceof Map) { + generator.writeStartObject(); + for (Map.Entry e : ((Map)json).entrySet()) { + generator.writeFieldName(e.getKey()); + Object value = e.getValue(); + encodeJson(value, generator); } - } else if (json instanceof Boolean) { - generator.writeBoolean((Boolean)json); - } else if (json instanceof Instant) { - // RFC-7493 - generator.writeString((ISO_INSTANT.format((Instant)json))); - } else if (json instanceof byte[]) { - // RFC-7493 - generator.writeString(BASE64_ENCODER.encodeToString((byte[]) json)); - } else if (json instanceof Buffer) { - // RFC-7493 - generator.writeString(BASE64_ENCODER.encodeToString(((Buffer) json).getBytes())); - } else if (json instanceof Enum) { - // vert.x extra (non standard but allowed conversion) - generator.writeString(((Enum) json).name()); - } else if (json == null) { - generator.writeNull(); - } else { + generator.writeEndObject(); + } else if (json instanceof List) { + generator.writeStartArray(); + for (Object item : (List) json) { + encodeJson(item, generator); + } + generator.writeEndArray(); + } else if (!encodeSingleType(generator, json)) { throw new EncodeException("Mapping " + json.getClass().getName() + " is not available without Jackson Databind on the classpath"); } } catch (IOException e) { @@ -333,6 +350,55 @@ private static void encodeJson(Object json, JsonGenerator generator) throws Enco } } + private static boolean encodeSingleType(JsonGenerator generator, Object json) throws IOException { + if (json == null) { + generator.writeNull(); + } else if (json instanceof String) { + generator.writeString((String) json); + } else if (json instanceof Number) { + encodeNumber(generator, json); + } else if (json instanceof Boolean) { + generator.writeBoolean((Boolean)json); + } else if (json instanceof Instant) { + // RFC-7493 + generator.writeString((ISO_INSTANT.format((Instant)json))); + } else if (json instanceof byte[]) { + // RFC-7493 + generator.writeString(BASE64_ENCODER.encodeToString((byte[]) json)); + } else if (json instanceof Buffer) { + // RFC-7493 + generator.writeString(BASE64_ENCODER.encodeToString(((Buffer) json).getBytes())); + } else if (json instanceof Enum) { + // vert.x extra (non standard but allowed conversion) + generator.writeString(((Enum) json).name()); + } else { + return false; + } + return true; + } + + private static void encodeNumber(JsonGenerator generator, Object json) throws IOException { + if (json instanceof Short) { + generator.writeNumber((Short) json); + } else if (json instanceof Integer) { + generator.writeNumber((Integer) json); + } else if (json instanceof Long) { + generator.writeNumber((Long) json); + } else if (json instanceof Float) { + generator.writeNumber((Float) json); + } else if (json instanceof Double) { + generator.writeNumber((Double) json); + } else if (json instanceof Byte) { + generator.writeNumber((Byte) json); + } else if (json instanceof BigInteger) { + generator.writeNumber((BigInteger) json); + } else if (json instanceof BigDecimal) { + generator.writeNumber((BigDecimal) json); + } else { + generator.writeNumber(((Number) json).doubleValue()); + } + } + private static Class classTypeOf(TypeReference typeRef) { Type type = typeRef.getType(); if (type instanceof Class) { diff --git a/src/main/java/io/vertx/core/loadbalancing/LoadBalancer.java b/src/main/java/io/vertx/core/loadbalancing/LoadBalancer.java new file mode 100644 index 00000000000..08ec32f423f --- /dev/null +++ b/src/main/java/io/vertx/core/loadbalancing/LoadBalancer.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.loadbalancing; + +import io.vertx.core.spi.loadbalancing.DefaultEndpointMetrics; +import io.vertx.core.spi.loadbalancing.Endpoint; +import io.vertx.core.spi.loadbalancing.EndpointMetrics; +import io.vertx.core.spi.loadbalancing.EndpointSelector; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A load balancer. + * + * A load balancer is stateless besides the configuration part. Effective load balancing can be achieved with + * {@link #selector()} which creates a stateful {@link EndpointSelector} implementing the load balancing policy. + * + * @author Julien Viet + */ +public interface LoadBalancer { + + /** + * Simple round-robin load balancer. + */ + LoadBalancer ROUND_ROBIN = () -> { + AtomicInteger idx = new AtomicInteger(); + return (EndpointSelector) endpoints -> { + int next = idx.getAndIncrement(); + return next % endpoints.size(); + }; + }; + + /** + * Least request load balancer. + */ + LoadBalancer LEAST_REQUESTS = () -> new EndpointSelector() { + @Override + public int selectEndpoint(List> endpoints) { + int numberOfRequests = Integer.MAX_VALUE; + int selected = -1; + int idx = 0; + for (Endpoint endpoint : endpoints) { + int val = ((DefaultEndpointMetrics)endpoint).numberOfInflightRequests(); + if (val < numberOfRequests) { + numberOfRequests = val; + selected = idx; + } + idx++; + } + return selected; + } + @Override + public Endpoint endpointOf(E endpoint) { + return new DefaultEndpointMetrics<>(endpoint); + } + }; + + /** + * Create a stateful endpoint selector. + * @return the selector + */ + EndpointSelector selector(); + +} diff --git a/src/main/java/io/vertx/core/logging/JULLogDelegate.java b/src/main/java/io/vertx/core/logging/JULLogDelegate.java index 5d336da2d86..aef71243cdd 100644 --- a/src/main/java/io/vertx/core/logging/JULLogDelegate.java +++ b/src/main/java/io/vertx/core/logging/JULLogDelegate.java @@ -45,109 +45,51 @@ public boolean isTraceEnabled() { return logger.isLoggable(Level.FINEST); } - public void fatal(final Object message) { - log(Level.SEVERE, message); - } - - public void fatal(final Object message, final Throwable t) { - log(Level.SEVERE, message, t); - } - public void error(final Object message) { log(Level.SEVERE, message); } - @Override - public void error(Object message, Object... params) { - log(Level.SEVERE, message, null, params); - } - public void error(final Object message, final Throwable t) { log(Level.SEVERE, message, t); } - @Override - public void error(Object message, Throwable t, Object... params) { - log(Level.SEVERE, message, t, params); - } - public void warn(final Object message) { log(Level.WARNING, message); } - @Override - public void warn(Object message, Object... params) { - log(Level.WARNING, message, null, params); - } - public void warn(final Object message, final Throwable t) { log(Level.WARNING, message, t); } - @Override - public void warn(Object message, Throwable t, Object... params) { - log(Level.WARNING, message, t, params); - } - public void info(final Object message) { log(Level.INFO, message); } - @Override - public void info(Object message, Object... params) { - log(Level.INFO, message, null, params); - } - public void info(final Object message, final Throwable t) { log(Level.INFO, message, t); } - @Override - public void info(Object message, Throwable t, Object... params) { - log(Level.INFO, message, t, params); - } - public void debug(final Object message) { log(Level.FINE, message); } - @Override - public void debug(Object message, Object... params) { - log(Level.FINE, message, null, params); - } - public void debug(final Object message, final Throwable t) { log(Level.FINE, message, t); } - @Override - public void debug(Object message, Throwable t, Object... params) { - log(Level.FINE, message, t, params); - } - public void trace(final Object message) { log(Level.FINEST, message); } - @Override - public void trace(Object message, Object... params) { - log(Level.FINEST, message, null, params); - } - public void trace(final Object message, final Throwable t) { log(Level.FINEST, message, t); } - @Override - public void trace(Object message, Throwable t, Object... params) { - log(Level.FINEST, message, t, params); - } - private void log(Level level, Object message) { log(level, message, null); } - private void log(Level level, Object message, Throwable t, Object... params) { + private void log(Level level, Object message, Throwable t) { if (!logger.isLoggable(level)) { return; } @@ -156,21 +98,13 @@ private void log(Level level, Object message, Throwable t, Object... params) { record.setLoggerName(logger.getName()); if (t != null) { record.setThrown(t); - } else if (params != null && params.length != 0 && params[params.length - 1] instanceof Throwable) { - // The exception may be the last parameters (SLF4J uses this convention). - record.setThrown((Throwable) params[params.length - 1]); } // This will disable stack trace lookup inside JUL. If someone wants location info, they can use their own formatter - // or use a different logging framework like sl4j, or log4j + // or use a different logging framework like slf4j, or log4j record.setSourceClassName(null); - record.setParameters(params); logger.log(record); } - private void log(Level level, Object message, Throwable t) { - log(level, message, t, (Object[]) null); - } - @Override public Object unwrap() { return logger; diff --git a/src/main/java/io/vertx/core/logging/JULLogDelegateFactory.java b/src/main/java/io/vertx/core/logging/JULLogDelegateFactory.java index 06a636e7fb3..b5da44f7f91 100644 --- a/src/main/java/io/vertx/core/logging/JULLogDelegateFactory.java +++ b/src/main/java/io/vertx/core/logging/JULLogDelegateFactory.java @@ -47,6 +47,7 @@ public boolean isAvailable() { return true; } + @Override public LogDelegate createDelegate(final String name) { return new JULLogDelegate(name); } diff --git a/src/main/java/io/vertx/core/logging/Log4j2LogDelegate.java b/src/main/java/io/vertx/core/logging/Log4j2LogDelegate.java index fd439f0be55..9c24a73893d 100644 --- a/src/main/java/io/vertx/core/logging/Log4j2LogDelegate.java +++ b/src/main/java/io/vertx/core/logging/Log4j2LogDelegate.java @@ -11,9 +11,9 @@ package io.vertx.core.logging; +import io.vertx.core.impl.logging.LoggerAdapter; import io.vertx.core.spi.logging.LogDelegate; import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.message.FormattedMessage; import org.apache.logging.log4j.message.Message; import org.apache.logging.log4j.spi.ExtendedLogger; @@ -26,8 +26,7 @@ public class Log4j2LogDelegate implements LogDelegate { final ExtendedLogger logger; - @SuppressWarnings("deprecation") - final static String FQCN = Logger.class.getCanonicalName(); + private static final String FQCN = LoggerAdapter.class.getCanonicalName(); Log4j2LogDelegate(final String name) { logger = (ExtendedLogger) org.apache.logging.log4j.LogManager.getLogger(name); @@ -38,116 +37,71 @@ public boolean isWarnEnabled() { return logger.isWarnEnabled(); } + @Override public boolean isInfoEnabled() { return logger.isInfoEnabled(); } + @Override public boolean isDebugEnabled() { return logger.isDebugEnabled(); } + @Override public boolean isTraceEnabled() { return logger.isTraceEnabled(); } - public void fatal(final Object message) { - log(Level.FATAL, message); - } - - public void fatal(final Object message, final Throwable t) { - log(Level.FATAL, message, t); - } - + @Override public void error(final Object message) { log(Level.ERROR, message); } @Override - public void error(Object message, Object... params) { - log(Level.ERROR, message.toString(), params); - } - public void error(final Object message, final Throwable t) { log(Level.ERROR, message, t); } @Override - public void error(Object message, Throwable t, Object... params) { - log(Level.ERROR, message.toString(), t, params); - } - public void warn(final Object message) { log(Level.WARN, message); } @Override - public void warn(Object message, Object... params) { - log(Level.WARN, message.toString(), params); - } - public void warn(final Object message, final Throwable t) { log(Level.WARN, message, t); } @Override - public void warn(Object message, Throwable t, Object... params) { - log(Level.WARN, message.toString(), t, params); - } - public void info(final Object message) { log(Level.INFO, message); } @Override - public void info(Object message, Object... params) { - log(Level.INFO, message.toString(), params); - } - public void info(final Object message, final Throwable t) { log(Level.INFO, message, t); } @Override - public void info(Object message, Throwable t, Object... params) { - log(Level.INFO, message.toString(), t, params); - } - public void debug(final Object message) { log(Level.DEBUG, message); } @Override - public void debug(Object message, Object... params) { - log(Level.DEBUG, message.toString(), params); - } - public void debug(final Object message, final Throwable t) { log(Level.DEBUG, message, t); } @Override - public void debug(Object message, Throwable t, Object... params) { - log(Level.DEBUG, message.toString(), t, params); - } - public void trace(final Object message) { log(Level.TRACE, message); } @Override - public void trace(Object message, Object... params) { - log(Level.TRACE, message.toString(), params); - } - public void trace(final Object message, final Throwable t) { log(Level.TRACE, message.toString(), t); } - @Override - public void trace(Object message, Throwable t, Object... params) { - log(Level.TRACE, message.toString(), t, params); - } - private void log(Level level, Object message) { log(level, message, null); } @@ -160,14 +114,6 @@ private void log(Level level, Object message, Throwable t) { } } - private void log(Level level, String message, Object... params) { - logger.logIfEnabled(FQCN, level, null, message, params); - } - - private void log(Level level, String message, Throwable t, Object... params) { - logger.logIfEnabled(FQCN, level, null, new FormattedMessage(message, params), t); - } - @Override public Object unwrap() { return logger; diff --git a/src/main/java/io/vertx/core/logging/Log4j2LogDelegateFactory.java b/src/main/java/io/vertx/core/logging/Log4j2LogDelegateFactory.java index 68d525f7268..fd12351b2d3 100644 --- a/src/main/java/io/vertx/core/logging/Log4j2LogDelegateFactory.java +++ b/src/main/java/io/vertx/core/logging/Log4j2LogDelegateFactory.java @@ -27,6 +27,7 @@ public boolean isAvailable() { return LogManager.getLogger(Log4j2LogDelegateFactory.class) != null; } + @Override public LogDelegate createDelegate(final String name) { return new Log4j2LogDelegate(name); } diff --git a/src/main/java/io/vertx/core/logging/Logger.java b/src/main/java/io/vertx/core/logging/Logger.java deleted file mode 100644 index cbb5fac0334..00000000000 --- a/src/main/java/io/vertx/core/logging/Logger.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.logging; - -import io.vertx.core.spi.logging.LogDelegate; - -/** - * This class allows us to isolate all our logging dependencies in one place. It also allows us to have zero runtime - * 3rd party logging jar dependencies, since we default to JUL by default. - *

- * By default logging will occur using JUL (Java-Util-Logging). The logging configuration file (logging.properties) - * used by JUL will taken from the default logging.properties in the JDK installation if no {@code java.util.logging.config.file} system - * property is set. - *

- * If you would prefer to use Log4J 2 or SLF4J instead of JUL then you can set a system property called - * {@code vertx.logger-delegate-factory-class-name} to the class name of the delegate for your logging system. - * For Log4J 2 the value is {@link io.vertx.core.logging.Log4j2LogDelegateFactory}, for SLF4J the value - * is {@link io.vertx.core.logging.SLF4JLogDelegateFactory}. You will need to ensure whatever jar files - * required by your favourite log framework are on your classpath. - *

- * Keep in mind that logging backends use different formats to represent replaceable tokens in parameterized messages. - * As a consequence, if you rely on parameterized logging methods, you won't be able to switch backends without changing your code. - * - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - * - * @author Tim Fox - */ -@Deprecated -public class Logger { - - final LogDelegate delegate; - - /** - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public Logger(final LogDelegate delegate) { - this.delegate = delegate; - } - - /** - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public boolean isWarnEnabled() { - return delegate.isWarnEnabled(); - } - - /** - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public boolean isInfoEnabled() { - return delegate.isInfoEnabled(); - } - - /** - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public boolean isDebugEnabled() { - return delegate.isDebugEnabled(); - } - - /** - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public boolean isTraceEnabled() { - return delegate.isTraceEnabled(); - } - - /** - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void fatal(final Object message) { - delegate.fatal(message); - } - - /** - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void fatal(final Object message, final Throwable t) { - delegate.fatal(message, t); - } - - /** - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void error(final Object message) { - delegate.error(message); - } - - /** - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void error(final Object message, final Throwable t) { - delegate.error(message, t); - } - - /** - * @throws UnsupportedOperationException if the logging backend does not support parameterized messages - * - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void error(final Object message, final Object... objects) { - delegate.error(message, objects); - } - - /** - * @throws UnsupportedOperationException if the logging backend does not support parameterized messages - * - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void error(final Object message, final Throwable t, final Object... objects) { - delegate.error(message, t, objects); - } - - /** - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void warn(final Object message) { - delegate.warn(message); - } - - /** - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void warn(final Object message, final Throwable t) { - delegate.warn(message, t); - } - - /** - * @throws UnsupportedOperationException if the logging backend does not support parameterized messages - * - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void warn(final Object message, final Object... objects) { - delegate.warn(message, objects); - } - - /** - * @throws UnsupportedOperationException if the logging backend does not support parameterized messages - * - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void warn(final Object message, final Throwable t, final Object... objects) { - delegate.warn(message, t, objects); - } - - /** - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void info(final Object message) { - delegate.info(message); - } - - /** - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void info(final Object message, final Throwable t) { - delegate.info(message, t); - } - - /** - * @throws UnsupportedOperationException if the logging backend does not support parameterized messages - * - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void info(final Object message, final Object... objects) { - delegate.info(message, objects); - } - - /** - * @throws UnsupportedOperationException if the logging backend does not support parameterized messages - * - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void info(final Object message, final Throwable t, final Object... objects) { - delegate.info(message, t, objects); - } - - /** - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void debug(final Object message) { - delegate.debug(message); - } - - /** - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void debug(final Object message, final Throwable t) { - delegate.debug(message, t); - } - - /** - * @throws UnsupportedOperationException if the logging backend does not support parameterized messages - * - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void debug(final Object message, final Object... objects) { - delegate.debug(message, objects); - } - - /** - * @throws UnsupportedOperationException if the logging backend does not support parameterized messages - * - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void debug(final Object message, final Throwable t, final Object... objects) { - delegate.debug(message, t, objects); - } - - /** - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void trace(final Object message) { - delegate.trace(message); - } - - /** - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void trace(final Object message, final Throwable t) { - delegate.trace(message, t); - } - - /** - * @throws UnsupportedOperationException if the logging backend does not support parameterized messages - * - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void trace(final Object message, final Object... objects) { - delegate.trace(message, objects); - } - - /** - * @throws UnsupportedOperationException if the logging backend does not support parameterized messages - * - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public void trace(final Object message, final Throwable t, final Object... objects) { - delegate.trace(message, t, objects); - } - - /** - * @return the delegate instance sending operations to the underlying logging framework - * - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public LogDelegate getDelegate() { - return delegate; - } -} diff --git a/src/main/java/io/vertx/core/logging/LoggerFactory.java b/src/main/java/io/vertx/core/logging/LoggerFactory.java deleted file mode 100644 index 7cf843ca7da..00000000000 --- a/src/main/java/io/vertx/core/logging/LoggerFactory.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.logging; - -import io.vertx.core.spi.logging.LogDelegate; -import io.vertx.core.spi.logging.LogDelegateFactory; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -/** - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - * - * @author Tim Fox - */ -public class LoggerFactory { - - public static final String LOGGER_DELEGATE_FACTORY_CLASS_NAME = "vertx.logger-delegate-factory-class-name"; - - private static volatile LogDelegateFactory delegateFactory; - - private static final ConcurrentMap loggers = new ConcurrentHashMap<>(); - - static { - initialise(); - // Do not log before being fully initialized (a logger extension may use Vert.x classes) - LogDelegate log = delegateFactory.createDelegate(LoggerFactory.class.getName()); - log.debug("Using " + delegateFactory.getClass().getName()); - } - - public static synchronized void initialise() { - ClassLoader loader = Thread.currentThread().getContextClassLoader(); - String className; - try { - className = System.getProperty(LOGGER_DELEGATE_FACTORY_CLASS_NAME); - } catch (Exception ignore) { - className = null; - } - if (className != null && configureWith(className, false, loader)) { - return; - } - if (loader.getResource("vertx-default-jul-logging.properties") == null) { - if (configureWith("SLF4J", true, loader) - || configureWith("Log4j2", true, loader)) { - return; - } - } - // Do not use dynamic classloading here to ensure the class is visible by AOT compilers - delegateFactory = new JULLogDelegateFactory(); - } - - private static boolean configureWith(String name, boolean shortName, ClassLoader loader) { - try { - Class clazz = Class.forName(shortName ? "io.vertx.core.logging." + name + "LogDelegateFactory" : name, true, loader); - LogDelegateFactory factory = (LogDelegateFactory) clazz.getDeclaredConstructor().newInstance(); - if (!factory.isAvailable()) { - return false; - } - delegateFactory = factory; - return true; - } catch (Throwable ignore) { - return false; - } - } - - /** - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public static Logger getLogger(final Class clazz) { - String name = clazz.isAnonymousClass() ? - clazz.getEnclosingClass().getCanonicalName() : - clazz.getCanonicalName(); - return getLogger(name); - } - - /** - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public static Logger getLogger(final String name) { - Logger logger = loggers.get(name); - - if (logger == null) { - LogDelegate delegate = delegateFactory.createDelegate(name); - - logger = new Logger(delegate); - - Logger oldLogger = loggers.putIfAbsent(name, logger); - - if (oldLogger != null) { - logger = oldLogger; - } - } - - return logger; - } - - /** - * @deprecated see https://github.com/eclipse-vertx/vert.x/issues/2774 - */ - @Deprecated - public static void removeLogger(String name) { - loggers.remove(name); - } -} diff --git a/src/main/java/io/vertx/core/logging/SLF4JLogDelegate.java b/src/main/java/io/vertx/core/logging/SLF4JLogDelegate.java index a475aba5d45..afbf3ce1e4e 100644 --- a/src/main/java/io/vertx/core/logging/SLF4JLogDelegate.java +++ b/src/main/java/io/vertx/core/logging/SLF4JLogDelegate.java @@ -15,28 +15,19 @@ import io.vertx.core.spi.logging.LogDelegate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.slf4j.helpers.FormattingTuple; -import org.slf4j.helpers.MessageFormatter; -import org.slf4j.spi.LocationAwareLogger; - -import static org.slf4j.spi.LocationAwareLogger.*; /** * @author Tim Fox */ public class SLF4JLogDelegate implements LogDelegate { - private static final Object[] EMPTY_PARAMETERS = {}; - - @SuppressWarnings("deprecation") - private static final String FQCN = io.vertx.core.logging.Logger.class.getCanonicalName(); - private final Logger logger; SLF4JLogDelegate(final String name) { logger = LoggerFactory.getLogger(name); } + // Visible for testing public SLF4JLogDelegate(Object logger) { this.logger = (Logger) logger; } @@ -46,178 +37,73 @@ public boolean isWarnEnabled() { return logger.isWarnEnabled(); } + @Override public boolean isInfoEnabled() { return logger.isInfoEnabled(); } + @Override public boolean isDebugEnabled() { return logger.isDebugEnabled(); } + @Override public boolean isTraceEnabled() { return logger.isTraceEnabled(); } - public void fatal(final Object message) { - log(ERROR_INT, message); - } - - public void fatal(final Object message, final Throwable t) { - log(ERROR_INT, message, t); - } - + @Override public void error(final Object message) { - log(ERROR_INT, message); + logger.error(valueOf(message)); } @Override - public void error(Object message, Object... params) { - log(ERROR_INT, message, null, params); - } - public void error(final Object message, final Throwable t) { - log(ERROR_INT, message, t); + logger.error(valueOf(message), t); } @Override - public void error(Object message, Throwable t, Object... params) { - log(ERROR_INT, message, t, params); - } - public void warn(final Object message) { - log(WARN_INT, message); + logger.warn(valueOf(message)); } @Override - public void warn(Object message, Object... params) { - log(WARN_INT, message, null, params); - } - public void warn(final Object message, final Throwable t) { - log(WARN_INT, message, t); + logger.warn(valueOf(message), t); } @Override - public void warn(Object message, Throwable t, Object... params) { - log(WARN_INT, message, t, params); - } - public void info(final Object message) { - log(INFO_INT, message); + logger.info(valueOf(message)); } @Override - public void info(Object message, Object... params) { - log(INFO_INT, message, null, params); - } - public void info(final Object message, final Throwable t) { - log(INFO_INT, message, t); + logger.info(valueOf(message), t); } @Override - public void info(Object message, Throwable t, Object... params) { - log(INFO_INT, message, t, params); - } - public void debug(final Object message) { - log(DEBUG_INT, message); - } - - public void debug(final Object message, final Object... params) { - log(DEBUG_INT, message, null, params); + logger.debug(valueOf(message)); } + @Override public void debug(final Object message, final Throwable t) { - log(DEBUG_INT, message, t); - } - - public void debug(final Object message, final Throwable t, final Object... params) { - log(DEBUG_INT, message, t, params); + logger.debug(valueOf(message), t); } + @Override public void trace(final Object message) { - log(TRACE_INT, message); + logger.trace(valueOf(message)); } @Override - public void trace(Object message, Object... params) { - log(TRACE_INT, message, null, params); - } - public void trace(final Object message, final Throwable t) { - log(TRACE_INT, message, t); + logger.trace(valueOf(message), t); } - @Override - public void trace(Object message, Throwable t, Object... params) { - log(TRACE_INT, message, t, params); - } - - private void log(int level, Object message) { - log(level, message, null); - } - - private void log(int level, Object message, Throwable t) { - log(level, message, t, (Object[]) null); - } - - private void log(int level, Object message, Throwable t, Object... params) { - String msg = (message == null) ? "NULL" : message.toString(); - - // We need to compute the right parameters. - // If we have both parameters and an error, we need to build a new array [params, t] - // If we don't have parameters, we need to build a new array [t] - // If we don't have error, it's just params. - Object[] parameters; - if (params == null) { - if (t == null) { - parameters = EMPTY_PARAMETERS; - } else { - parameters = new Object[]{t}; - } - } else { - if (t == null) { - parameters = params; - } else { - parameters = new Object[params.length + 1]; - System.arraycopy(params, 0, parameters, 0, params.length); - parameters[params.length] = t; - } - } - - if (logger instanceof LocationAwareLogger) { - // make sure we don't format the objects if we don't log the line anyway - if (level == TRACE_INT && logger.isTraceEnabled() || - level == DEBUG_INT && logger.isDebugEnabled() || - level == INFO_INT && logger.isInfoEnabled() || - level == WARN_INT && logger.isWarnEnabled() || - level == ERROR_INT && logger.isErrorEnabled()) { - LocationAwareLogger l = (LocationAwareLogger) logger; - FormattingTuple ft = MessageFormatter.arrayFormat(msg, parameters); - l.log(null, FQCN, level, ft.getMessage(), null, ft.getThrowable()); - } - } else { - switch (level) { - case TRACE_INT: - logger.trace(msg, parameters); - break; - case DEBUG_INT: - logger.debug(msg, parameters); - break; - case INFO_INT: - logger.info(msg, parameters); - break; - case WARN_INT: - logger.warn(msg, parameters); - break; - case ERROR_INT: - logger.error(msg, parameters); - break; - default: - throw new IllegalArgumentException("Unknown log level " + level); - } - } + private static String valueOf(Object message) { + return message == null ? "NULL" : message.toString(); } @Override diff --git a/src/main/java/io/vertx/core/logging/SLF4JLogDelegateFactory.java b/src/main/java/io/vertx/core/logging/SLF4JLogDelegateFactory.java index 65a091331e9..071728a7fd2 100644 --- a/src/main/java/io/vertx/core/logging/SLF4JLogDelegateFactory.java +++ b/src/main/java/io/vertx/core/logging/SLF4JLogDelegateFactory.java @@ -45,6 +45,7 @@ public boolean isAvailable() { return !(fact instanceof NOPLoggerFactory); } + @Override public LogDelegate createDelegate(final String clazz) { return new SLF4JLogDelegate(clazz); } diff --git a/src/main/java/io/vertx/core/metrics/MetricsOptions.java b/src/main/java/io/vertx/core/metrics/MetricsOptions.java index d9f5462d467..63e5976e036 100644 --- a/src/main/java/io/vertx/core/metrics/MetricsOptions.java +++ b/src/main/java/io/vertx/core/metrics/MetricsOptions.java @@ -12,8 +12,8 @@ package io.vertx.core.metrics; import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.json.JsonObject; -import io.vertx.core.spi.VertxMetricsFactory; /** * Vert.x metrics base configuration, this class can be extended by provider implementations to configure @@ -21,7 +21,8 @@ * * @author Julien Viet */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public class MetricsOptions { /** @@ -31,7 +32,6 @@ public class MetricsOptions { private boolean enabled; private JsonObject json; // Keep a copy of the original json, so we don't lose info when building options subclasses - private VertxMetricsFactory factory; /** * Default constructor @@ -47,7 +47,6 @@ public MetricsOptions() { */ public MetricsOptions(MetricsOptions other) { enabled = other.isEnabled(); - factory = other.factory; } /** @@ -81,36 +80,6 @@ public MetricsOptions setEnabled(boolean enable) { return this; } - /** - * Get the metrics factory to be used when metrics are enabled. - *

- * If the metrics factory has been programmatically set here, then that will be used when metrics are enabled - * for creating the {@link io.vertx.core.spi.metrics.VertxMetrics} instance. - *

- * Otherwise Vert.x attempts to locate a metrics factory implementation on the classpath. - * - * @return the metrics factory - */ - public VertxMetricsFactory getFactory() { - return factory; - } - - /** - * Programmatically set the metrics factory to be used when metrics are enabled. - *

- * Only valid if {@link MetricsOptions#isEnabled} = true. - *

- * Normally Vert.x will look on the classpath for a metrics factory implementation, but if you want to set one - * programmatically you can use this method. - * - * @param factory the metrics factory - * @return a reference to this, so the API can be used fluently - */ - public MetricsOptions setFactory(VertxMetricsFactory factory) { - this.factory = factory; - return this; - } - public JsonObject toJson() { JsonObject json = this.json; if (json == null) { diff --git a/src/main/java/io/vertx/core/net/Address.java b/src/main/java/io/vertx/core/net/Address.java index 00c52367eb5..6d0ef06a25e 100644 --- a/src/main/java/io/vertx/core/net/Address.java +++ b/src/main/java/io/vertx/core/net/Address.java @@ -10,13 +10,13 @@ */ package io.vertx.core.net; -import io.vertx.codegen.annotations.VertxGen; +import io.vertx.codegen.annotations.DataObject; /** * A general purpose networking address. * * @author Julien Viet */ -@VertxGen +@DataObject public interface Address { } diff --git a/src/main/java/io/vertx/core/net/AddressResolver.java b/src/main/java/io/vertx/core/net/AddressResolver.java new file mode 100644 index 00000000000..7a2fc9840fb --- /dev/null +++ b/src/main/java/io/vertx/core/net/AddressResolver.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.net; + +import io.vertx.core.Vertx; + +/** + * A generic address resolver market interface. Implementation must also implement the SPI interface {@link io.vertx.core.spi.resolver.address.AddressResolver} + * and can be cast to this type. + */ +public interface AddressResolver { + + /** + * Return a resolver capable of resolving addresses for a client. + * + * @param vertx the vertx instance + * @return the resolver + */ + io.vertx.core.spi.resolver.address.AddressResolver resolver(Vertx vertx); + +} diff --git a/src/main/java/io/vertx/core/net/ClientOptionsBase.java b/src/main/java/io/vertx/core/net/ClientOptionsBase.java index 6565adeb9fc..322b63ac89b 100755 --- a/src/main/java/io/vertx/core/net/ClientOptionsBase.java +++ b/src/main/java/io/vertx/core/net/ClientOptionsBase.java @@ -12,6 +12,8 @@ package io.vertx.core.net; import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.annotations.GenIgnore; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.buffer.Buffer; import io.vertx.core.json.JsonObject; import io.netty.handler.logging.ByteBufFormat; @@ -25,26 +27,21 @@ * * @author Tim Fox */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public abstract class ClientOptionsBase extends TCPSSLOptions { /** - * The default value of connect timeout = 60000 ms + * The default value of connect timeout = 60000 (ms) */ public static final int DEFAULT_CONNECT_TIMEOUT = 60000; - /** - * The default value of whether all servers (SSL/TLS) should be trusted = false - */ - public static final boolean DEFAULT_TRUST_ALL = false; - /** * The default value of the client metrics = "": */ public static final String DEFAULT_METRICS_NAME = ""; private int connectTimeout; - private boolean trustAll; private String metricsName; private ProxyOptions proxyOptions; private String localAddress; @@ -66,7 +63,6 @@ public ClientOptionsBase() { public ClientOptionsBase(ClientOptionsBase other) { super(other); this.connectTimeout = other.getConnectTimeout(); - this.trustAll = other.isTrustAll(); this.metricsName = other.metricsName; this.proxyOptions = other.proxyOptions != null ? new ProxyOptions(other.proxyOptions) : null; this.localAddress = other.localAddress; @@ -97,18 +93,29 @@ public JsonObject toJson() { private void init() { this.connectTimeout = DEFAULT_CONNECT_TIMEOUT; - this.trustAll = DEFAULT_TRUST_ALL; this.metricsName = DEFAULT_METRICS_NAME; this.proxyOptions = null; this.localAddress = null; } + @GenIgnore + @Override + public ClientSSLOptions getSslOptions() { + return (ClientSSLOptions) super.getSslOptions(); + } + + @Override + protected ClientSSLOptions getOrCreateSSLOptions() { + return (ClientSSLOptions) super.getOrCreateSSLOptions(); + } + /** * * @return true if all server certificates should be trusted */ public boolean isTrustAll() { - return trustAll; + ClientSSLOptions o = getSslOptions(); + return o != null ? o.isTrustAll() : ClientSSLOptions.DEFAULT_TRUST_ALL; } /** @@ -118,7 +125,7 @@ public boolean isTrustAll() { * @return a reference to this, so the API can be used fluently */ public ClientOptionsBase setTrustAll(boolean trustAll) { - this.trustAll = trustAll; + getOrCreateSSLOptions().setTrustAll(trustAll); return this; } @@ -292,41 +299,11 @@ public ClientOptionsBase setKeyCertOptions(KeyCertOptions options) { return (ClientOptionsBase) super.setKeyCertOptions(options); } - @Override - public ClientOptionsBase setKeyStoreOptions(JksOptions options) { - return (ClientOptionsBase) super.setKeyStoreOptions(options); - } - - @Override - public ClientOptionsBase setPfxKeyCertOptions(PfxOptions options) { - return (ClientOptionsBase) super.setPfxKeyCertOptions(options); - } - - @Override - public ClientOptionsBase setPemKeyCertOptions(PemKeyCertOptions options) { - return (ClientOptionsBase) super.setPemKeyCertOptions(options); - } - @Override public ClientOptionsBase setTrustOptions(TrustOptions options) { return (ClientOptionsBase) super.setTrustOptions(options); } - @Override - public ClientOptionsBase setTrustStoreOptions(JksOptions options) { - return (ClientOptionsBase) super.setTrustStoreOptions(options); - } - - @Override - public ClientOptionsBase setPfxTrustOptions(PfxOptions options) { - return (ClientOptionsBase) super.setPfxTrustOptions(options); - } - - @Override - public ClientOptionsBase setPemTrustOptions(PemTrustOptions options) { - return (ClientOptionsBase) super.setPemTrustOptions(options); - } - @Override public ClientOptionsBase setUseAlpn(boolean useAlpn) { return (ClientOptionsBase) super.setUseAlpn(useAlpn); @@ -337,16 +314,6 @@ public ClientOptionsBase setSslEngineOptions(SSLEngineOptions sslEngineOptions) return (ClientOptionsBase) super.setSslEngineOptions(sslEngineOptions); } - @Override - public ClientOptionsBase setJdkSslEngineOptions(JdkSSLEngineOptions sslEngineOptions) { - return (ClientOptionsBase) super.setJdkSslEngineOptions(sslEngineOptions); - } - - @Override - public ClientOptionsBase setOpenSslEngineOptions(OpenSSLEngineOptions sslEngineOptions) { - return (ClientOptionsBase) super.setOpenSslEngineOptions(sslEngineOptions); - } - @Override public ClientOptionsBase setSendBufferSize(int sendBufferSize) { return (ClientOptionsBase) super.setSendBufferSize(sendBufferSize); diff --git a/src/main/java/io/vertx/core/net/ClientSSLOptions.java b/src/main/java/io/vertx/core/net/ClientSSLOptions.java new file mode 100644 index 00000000000..d12d6cddc68 --- /dev/null +++ b/src/main/java/io/vertx/core/net/ClientSSLOptions.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.net; + +import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; +import io.vertx.core.json.JsonObject; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * Client SSL options. + */ +@DataObject +@JsonGen(publicConverter = false) +public class ClientSSLOptions extends SSLOptions { + + /** + * Default value to determine hostname verification algorithm hostname verification (for SSL/TLS) = "" + */ + public static final String DEFAULT_HOSTNAME_VERIFICATION_ALGORITHM = ""; + + /** + * The default value of whether all servers (SSL/TLS) should be trusted = false + */ + public static final boolean DEFAULT_TRUST_ALL = false; + + private String hostnameVerificationAlgorithm; + private boolean trustAll; + + /** + * Default constructor + */ + public ClientSSLOptions() { + } + + /** + * Copy constructor + * + * @param other the options to copy + */ + public ClientSSLOptions(ClientSSLOptions other) { + super(other); + + hostnameVerificationAlgorithm = other.getHostnameVerificationAlgorithm(); + trustAll = other.isTrustAll(); + } + + /** + * Create options from JSON + * + * @param json the JSON + */ + public ClientSSLOptions(JsonObject json) { + super(json); + ClientSSLOptionsConverter.fromJson(json, this); + } + + @Override + protected void init() { + super.init(); + hostnameVerificationAlgorithm = DEFAULT_HOSTNAME_VERIFICATION_ALGORITHM; + trustAll = DEFAULT_TRUST_ALL; + } + + @Override + public ClientSSLOptions copy() { + return new ClientSSLOptions(this); + } + + /** + * @return the value of the hostname verification algorithm + */ + public String getHostnameVerificationAlgorithm() { + return hostnameVerificationAlgorithm; + } + + /** + * Set the hostname verification algorithm interval + * + * @param hostnameVerificationAlgorithm should be HTTPS, LDAPS or a {@code null} + * @return a reference to this, so the API can be used fluently + */ + public ClientSSLOptions setHostnameVerificationAlgorithm(String hostnameVerificationAlgorithm) { + this.hostnameVerificationAlgorithm = hostnameVerificationAlgorithm; + return this; + } + + /** + * @return {@code true} if all server certificates should be trusted + */ + public boolean isTrustAll() { + return trustAll; + } + + /** + * Set whether all server certificates should be trusted + * + * @param trustAll {@code true} if all should be trusted + * @return a reference to this, so the API can be used fluently + */ + public ClientSSLOptions setTrustAll(boolean trustAll) { + this.trustAll = trustAll; + return this; + } + + @Override + public ClientSSLOptions setKeyCertOptions(KeyCertOptions options) { + return (ClientSSLOptions) super.setKeyCertOptions(options); + } + + @Override + public ClientSSLOptions setTrustOptions(TrustOptions options) { + return (ClientSSLOptions) super.setTrustOptions(options); + } + + @Override + public ClientSSLOptions setUseAlpn(boolean useAlpn) { + return (ClientSSLOptions) super.setUseAlpn(useAlpn); + } + + @Override + public ClientSSLOptions setSslHandshakeTimeout(long sslHandshakeTimeout) { + return (ClientSSLOptions) super.setSslHandshakeTimeout(sslHandshakeTimeout); + } + + @Override + public ClientSSLOptions setSslHandshakeTimeoutUnit(TimeUnit sslHandshakeTimeoutUnit) { + return (ClientSSLOptions) super.setSslHandshakeTimeoutUnit(sslHandshakeTimeoutUnit); + } + + @Override + public ClientSSLOptions setEnabledSecureTransportProtocols(Set enabledSecureTransportProtocols) { + return (ClientSSLOptions) super.setEnabledSecureTransportProtocols(enabledSecureTransportProtocols); + } + + @Override + public ClientSSLOptions setApplicationLayerProtocols(List protocols) { + return (ClientSSLOptions) super.setApplicationLayerProtocols(protocols); + } + + /** + * Convert to JSON + * + * @return the JSON + */ + public JsonObject toJson() { + JsonObject json = super.toJson(); + ClientSSLOptionsConverter.toJson(this, json); + return json; + } +} diff --git a/src/main/java/io/vertx/core/net/ConnectOptions.java b/src/main/java/io/vertx/core/net/ConnectOptions.java new file mode 100644 index 00000000000..675663d3ceb --- /dev/null +++ b/src/main/java/io/vertx/core/net/ConnectOptions.java @@ -0,0 +1,227 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.net; + +import io.vertx.codegen.annotations.DataObject; + +/** + * Options for configuring how to connect to a TCP server. + * + * @author Julien Viet + */ +@DataObject +public class ConnectOptions { + + /** + * SSL enable by default = false + */ + public static final boolean DEFAULT_SSL = false; + + private String host; + private Integer port; + private String sniServerName; + private SocketAddress remoteAddress; + private ProxyOptions proxyOptions; + private boolean ssl; + private ClientSSLOptions sslOptions; + private int timeout; + + /** + * The default constructor + */ + public ConnectOptions() { + host = null; + port = null; + sniServerName = null; + remoteAddress = null; + proxyOptions = null; + ssl = DEFAULT_SSL; + sslOptions = null; + timeout = -1; + } + + /** + * Copy constructor + * + * @param other the options to copy + */ + public ConnectOptions(ConnectOptions other) { + host = other.getHost(); + port = other.getPort(); + sniServerName = other.getSniServerName(); + remoteAddress = other.getRemoteAddress(); + proxyOptions = other.getProxyOptions() != null ? new ProxyOptions(other.getProxyOptions()) : null; + ssl = other.isSsl(); + sslOptions = other.getSslOptions() != null ? new ClientSSLOptions(other.getSslOptions()) : null; + timeout = other.getTimeout(); + } + + /** + * Get the host name to be used by the client connection. + * + * @return the host name + */ + public String getHost() { + return host; + } + + /** + * Set the host name to be used by the client connection. + * + * @return a reference to this, so the API can be used fluently + */ + public ConnectOptions setHost(String host) { + this.host = host; + return this; + } + + /** + * Get the port to be used by the client connection. + * + * @return the port + */ + public Integer getPort() { + return port; + } + + /** + * Set the port to be used by the client connection. + * + * @return a reference to this, so the API can be used fluently + */ + public ConnectOptions setPort(Integer port) { + this.port = port; + return this; + } + + /** + * Get the remote address to connect to, if none is provided {@link #host}/{@link #port} wille be used. + * + * @return the remote address + */ + public SocketAddress getRemoteAddress() { + return remoteAddress; + } + + /** + * Set the remote address to be used by the client connection. + * + *

When the server address is {@code null}, the address will be resolved after the {@link #host} + * property by the Vert.x resolver and the {@link #port} will be used. + * + *

Use this when you want to connect to a specific server address without name resolution or use a domain socket. + * + * @return a reference to this, so the API can be used fluently + */ + public ConnectOptions setRemoteAddress(SocketAddress remoteAddress) { + this.remoteAddress = remoteAddress; + return this; + } + + /** + * @return the SNI (server name indication) server name + */ + public String getSniServerName() { + return sniServerName; + } + + /** + * Set the SNI server name to use. + * + * @param sniServerName the server name + * @return a reference to this, so the API can be used fluently + */ + public ConnectOptions setSniServerName(String sniServerName) { + this.sniServerName = sniServerName; + return this; + } + + /** + * Get proxy options for connections + * + * @return proxy options + */ + public ProxyOptions getProxyOptions() { + return proxyOptions; + } + + /** + * Set proxy options for connections via CONNECT proxy (e.g. Squid) or a SOCKS proxy. + *

+ * When none is provided, the {@link NetClientOptions} proxy options will be used instead. + * + * @param proxyOptions proxy options object + * @return a reference to this, so the API can be used fluently + */ + public ConnectOptions setProxyOptions(ProxyOptions proxyOptions) { + this.proxyOptions = proxyOptions; + return this; + } + + /** + * @return is SSL/TLS enabled? + */ + public boolean isSsl() { + return ssl; + } + + /** + * Set whether SSL/TLS is enabled. + * + * @param ssl {@code true} if enabled + * @return a reference to this, so the API can be used fluently + */ + public ConnectOptions setSsl(boolean ssl) { + this.ssl = ssl; + return this; + } + + /** + * @return the SSL options + */ + public ClientSSLOptions getSslOptions() { + return sslOptions; + } + + /** + * Set the SSL options to use. + *

+ * When none is provided, the {@link NetClientOptions} SSL options will be used instead. + * @param sslOptions the SSL options to use + * @return a reference to this, so the API can be used fluently + */ + public ConnectOptions setSslOptions(ClientSSLOptions sslOptions) { + this.sslOptions = sslOptions; + return this; + } + + /** + * @return the value of connect timeout in millis or {@code -1} when using the client defined connect timeout {@link NetClientOptions#getConnectTimeout()} + */ + public int getTimeout() { + return timeout; + } + + /** + * Override the client connect timeout in millis when {@code timeout >= 0} or use the client defined connect timeout {@link NetClientOptions#getConnectTimeout()} + * when {@code timeout == -1}. + * + * @param timeout connect timeout, in ms + * @return a reference to this, so the API can be used fluently + */ + public ConnectOptions setTimeout(int timeout) { + if (timeout < -2) { + throw new IllegalArgumentException("connectTimeout must be >= 0 or -1 (use default client value)"); + } + this.timeout = timeout; + return this; + } +} diff --git a/src/main/java/io/vertx/core/net/HostAndPort.java b/src/main/java/io/vertx/core/net/HostAndPort.java new file mode 100644 index 00000000000..4b06a692177 --- /dev/null +++ b/src/main/java/io/vertx/core/net/HostAndPort.java @@ -0,0 +1,87 @@ +package io.vertx.core.net; + +import io.vertx.codegen.annotations.DataObject; +import io.vertx.core.http.impl.HttpUtils; +import io.vertx.core.json.JsonObject; +import io.vertx.core.net.impl.HostAndPortImpl; + +/** + * A combination of host and port. + */ +@DataObject +public interface HostAndPort { + + static HostAndPort fromJson(JsonObject json) { + int port = json.getInteger("port", -1); + String host = json.getString("host"); + if (host != null) { + return HostAndPort.create(host, port); + } + return null; + } + + /** + * Create an arbitrary instance. + * + * @param host the host value + * @param port the port value + * @return the instance. + */ + static HostAndPort create(String host, int port) { + return new HostAndPortImpl(host, port); + } + + /** + * Parse an authority HTTP header, that is host [':' port], according to + * rfc3986. + * + * @param string the string to parse + * @param schemePort the scheme port used when the optional port is not specified + * @return the parsed authority or {@code null} when the {@code string} does not represent a valid authority. + */ + static HostAndPort parseAuthority(String string, int schemePort) { + return HostAndPortImpl.parseAuthority(string, schemePort); + } + + /** + * Create an instance with a valid {@code host} for a valid authority: the {@code host} must + * match the host rule of rfc3986. + * + * @param host the host portion + * @param port the port + * @return the instance + */ + static HostAndPort authority(String host, int port) { + if (!HttpUtils.isValidHostAuthority(host)) { + throw new IllegalArgumentException("Invalid authority host portion: " + host); + } + return new HostAndPortImpl(host, port); + } + + /** + * Like {@link #authority(String, int)} without a port, {@code -1} is used instead. + */ + static HostAndPort authority(String host) { + return authority(host, -1); + } + + /** + * @return the host value + */ + String host(); + + /** + * @return the port value or {@code -1} when not specified + */ + int port(); + + default JsonObject toJson() { + JsonObject json = new JsonObject().put("host", host()); + int port = port(); + if (port > 0) { + json.put("port", port); + } + return json; + } + +} diff --git a/src/main/java/io/vertx/core/net/JksOptions.java b/src/main/java/io/vertx/core/net/JksOptions.java index 7272bbd76e3..2f32495a69d 100644 --- a/src/main/java/io/vertx/core/net/JksOptions.java +++ b/src/main/java/io/vertx/core/net/JksOptions.java @@ -12,6 +12,7 @@ package io.vertx.core.net; import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.buffer.Buffer; import io.vertx.core.json.JsonObject; @@ -21,7 +22,8 @@ * @author Julien Viet * @author Tim Fox */ -@DataObject(generateConverter = true) +@DataObject +@JsonGen(publicConverter = false) public class JksOptions extends KeyStoreOptionsBase { /** diff --git a/src/main/java/io/vertx/core/net/KeyManagerFactoryOptions.java b/src/main/java/io/vertx/core/net/KeyManagerFactoryOptions.java index b72b5ca8402..21e90fac8d9 100644 --- a/src/main/java/io/vertx/core/net/KeyManagerFactoryOptions.java +++ b/src/main/java/io/vertx/core/net/KeyManagerFactoryOptions.java @@ -16,6 +16,7 @@ import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.X509KeyManager; +import java.util.Objects; import java.util.function.Function; /** @@ -84,4 +85,21 @@ public KeyManagerFactory getKeyManagerFactory(Vertx vertx) { public Function keyManagerFactoryMapper(Vertx vertx) throws Exception { return name -> null; } + + @Override + public int hashCode() { + return keyManagerFactory.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof KeyManagerFactoryOptions) { + KeyManagerFactoryOptions that = (KeyManagerFactoryOptions) obj; + return Objects.equals(keyManagerFactory, that.keyManagerFactory); + } + return false; + } } diff --git a/src/main/java/io/vertx/core/net/KeyStoreOptions.java b/src/main/java/io/vertx/core/net/KeyStoreOptions.java index 192648aad53..83f5f91979d 100644 --- a/src/main/java/io/vertx/core/net/KeyStoreOptions.java +++ b/src/main/java/io/vertx/core/net/KeyStoreOptions.java @@ -12,6 +12,7 @@ package io.vertx.core.net; import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.buffer.Buffer; import io.vertx.core.json.JsonObject; @@ -48,7 +49,8 @@ * @author Julien Viet * @author Tim Fox */ -@DataObject(generateConverter = true) +@DataObject +@JsonGen(publicConverter = false) public class KeyStoreOptions extends KeyStoreOptionsBase { /** diff --git a/src/main/java/io/vertx/core/net/KeyStoreOptionsBase.java b/src/main/java/io/vertx/core/net/KeyStoreOptionsBase.java index 9e60de28b1c..c51d9f790ea 100644 --- a/src/main/java/io/vertx/core/net/KeyStoreOptionsBase.java +++ b/src/main/java/io/vertx/core/net/KeyStoreOptionsBase.java @@ -245,4 +245,16 @@ public boolean equals(Object obj) { } return false; } + + @Override + public int hashCode() { + int hashCode = Objects.hashCode(provider); + hashCode = 31 * hashCode + Objects.hashCode(type); + hashCode = 31 * hashCode + Objects.hashCode(password); + hashCode = 31 * hashCode + Objects.hashCode(path); + hashCode = 31 * hashCode + Objects.hashCode(value); + hashCode = 31 * hashCode + Objects.hashCode(alias); + hashCode = 31 * hashCode + Objects.hashCode(aliasPassword); + return hashCode; + } } diff --git a/src/main/java/io/vertx/core/net/NetClient.java b/src/main/java/io/vertx/core/net/NetClient.java index 8e93ba5e701..dd438129e26 100644 --- a/src/main/java/io/vertx/core/net/NetClient.java +++ b/src/main/java/io/vertx/core/net/NetClient.java @@ -11,8 +11,10 @@ package io.vertx.core.net; +import io.vertx.core.AsyncResult; import io.vertx.core.Future; import io.vertx.codegen.annotations.VertxGen; +import io.vertx.core.Handler; import io.vertx.core.metrics.Measured; import java.util.concurrent.TimeUnit; @@ -76,6 +78,16 @@ public interface NetClient extends Measured { */ Future connect(SocketAddress remoteAddress, String serverName); + /** + * Open a connection to a server at the specific {@code connectOptions}. + *

+ * The connect is done asynchronously and on success, a {@link NetSocket} instance is supplied via the {@code connectHandler} instance + * + * @param connectOptions the options describing how to connect to the remote server + * @return a future notified when the socket is connected + */ + Future connect(ConnectOptions connectOptions); + /** * Close the client. *

@@ -83,16 +95,44 @@ public interface NetClient extends Measured { * complete until some time after the method has returned. * @return a future notified when the client is closed */ - Future close(); + default Future close() { + return close(0L, TimeUnit.SECONDS); + } + + /** + * Initiate the client close sequence. + * + *

Connections are taken out of service and notified the close sequence has started through {@link NetSocket#shutdownHandler(Handler)}. + * When all connections are closed the client is closed. When the timeout expires, all unclosed connections are immediately closed. + */ + Future close(long timeout, TimeUnit unit); /** - * Update the client SSL options. + *

Update the client with new SSL {@code options}, the update happens if the options object is valid and different + * from the existing options object. * - * Update only happens if the SSL options is valid. + *

The boolean succeeded future result indicates whether the update occurred. * * @param options the new SSL options * @return a future signaling the update success */ - Future updateSSLOptions(SSLOptions options); + default Future updateSSLOptions(ClientSSLOptions options) { + return updateSSLOptions(options, false); + } + /** + *

Update the client with new SSL {@code options}, the update happens if the options object is valid and different + * from the existing options object. + * + *

The {@code options} object is compared using its {@code equals} method against the existing options to prevent + * an update when the objects are equals since loading options can be costly, this can happen for share TCP servers. + * When object are equals, setting {@code force} to {@code true} forces the update. + * + *

The boolean succeeded future result indicates whether the update occurred. + * + * @param options the new SSL options + * @param force force the update when options are equals + * @return a future signaling the update success + */ + Future updateSSLOptions(ClientSSLOptions options, boolean force); } diff --git a/src/main/java/io/vertx/core/net/NetClientOptions.java b/src/main/java/io/vertx/core/net/NetClientOptions.java index a42bf0db727..7f2c3b56265 100755 --- a/src/main/java/io/vertx/core/net/NetClientOptions.java +++ b/src/main/java/io/vertx/core/net/NetClientOptions.java @@ -13,10 +13,10 @@ import io.netty.handler.logging.ByteBufFormat; import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.buffer.Buffer; import io.vertx.core.json.JsonObject; -import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Set; @@ -27,7 +27,8 @@ * * @author Tim Fox */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public class NetClientOptions extends ClientOptionsBase { /** @@ -52,8 +53,6 @@ public class NetClientOptions extends ClientOptionsBase { private int reconnectAttempts; private long reconnectInterval; - private String hostnameVerificationAlgorithm; - private List applicationLayerProtocols; private boolean registerWriteHandler; /** @@ -73,8 +72,6 @@ public NetClientOptions(NetClientOptions other) { super(other); this.reconnectAttempts = other.getReconnectAttempts(); this.reconnectInterval = other.getReconnectInterval(); - this.hostnameVerificationAlgorithm = other.getHostnameVerificationAlgorithm(); - this.applicationLayerProtocols = other.applicationLayerProtocols != null ? new ArrayList<>(other.applicationLayerProtocols) : null; this.registerWriteHandler = other.registerWriteHandler; } @@ -101,7 +98,6 @@ public NetClientOptions(JsonObject json) { private void init() { this.reconnectAttempts = DEFAULT_RECONNECT_ATTEMPTS; this.reconnectInterval = DEFAULT_RECONNECT_INTERVAL; - this.hostnameVerificationAlgorithm = DEFAULT_HOSTNAME_VERIFICATION_ALGORITHM; this.registerWriteHandler = DEFAULT_REGISTER_WRITE_HANDLER; } @@ -189,44 +185,12 @@ public NetClientOptions setKeyCertOptions(KeyCertOptions options) { return this; } - @Override - public NetClientOptions setKeyStoreOptions(JksOptions options) { - super.setKeyStoreOptions(options); - return this; - } - - @Override - public NetClientOptions setPfxKeyCertOptions(PfxOptions options) { - return (NetClientOptions) super.setPfxKeyCertOptions(options); - } - - @Override - public NetClientOptions setPemKeyCertOptions(PemKeyCertOptions options) { - return (NetClientOptions) super.setPemKeyCertOptions(options); - } - @Override public NetClientOptions setTrustOptions(TrustOptions options) { super.setTrustOptions(options); return this; } - @Override - public NetClientOptions setTrustStoreOptions(JksOptions options) { - super.setTrustStoreOptions(options); - return this; - } - - @Override - public NetClientOptions setPemTrustOptions(PemTrustOptions options) { - return (NetClientOptions) super.setPemTrustOptions(options); - } - - @Override - public NetClientOptions setPfxTrustOptions(PfxOptions options) { - return (NetClientOptions) super.setPfxTrustOptions(options); - } - @Override public NetClientOptions addEnabledCipherSuite(String suite) { super.addEnabledCipherSuite(suite); @@ -260,11 +224,6 @@ public NetClientOptions setSslEngineOptions(SSLEngineOptions sslEngineOptions) { return (NetClientOptions) super.setSslEngineOptions(sslEngineOptions); } - @Override - public NetClientOptions setJdkSslEngineOptions(JdkSSLEngineOptions sslEngineOptions) { - return (NetClientOptions) super.setJdkSslEngineOptions(sslEngineOptions); - } - @Override public NetClientOptions setTcpFastOpen(boolean tcpFastOpen) { return (NetClientOptions) super.setTcpFastOpen(tcpFastOpen); @@ -280,11 +239,6 @@ public NetClientOptions setTcpQuickAck(boolean tcpQuickAck) { return (NetClientOptions) super.setTcpQuickAck(tcpQuickAck); } - @Override - public ClientOptionsBase setOpenSslEngineOptions(OpenSSLEngineOptions sslEngineOptions) { - return super.setOpenSslEngineOptions(sslEngineOptions); - } - @Override public NetClientOptions addCrlPath(String crlPath) throws NullPointerException { return (NetClientOptions) super.addCrlPath(crlPath); @@ -350,9 +304,9 @@ public NetClientOptions setReconnectInterval(long interval) { /** * @return the value of the hostname verification algorithm */ - public String getHostnameVerificationAlgorithm() { - return hostnameVerificationAlgorithm; + ClientSSLOptions o = getSslOptions(); + return o != null ? o.getHostnameVerificationAlgorithm() : DEFAULT_HOSTNAME_VERIFICATION_ALGORITHM; } /** @@ -365,7 +319,7 @@ public String getHostnameVerificationAlgorithm() { public NetClientOptions setHostnameVerificationAlgorithm(String hostnameVerificationAlgorithm) { Objects.requireNonNull(hostnameVerificationAlgorithm, "hostnameVerificationAlgorithm can not be null!"); - this.hostnameVerificationAlgorithm = hostnameVerificationAlgorithm; + getOrCreateSSLOptions().setHostnameVerificationAlgorithm(hostnameVerificationAlgorithm); return this; } @@ -373,7 +327,8 @@ public NetClientOptions setHostnameVerificationAlgorithm(String hostnameVerifica * @return the list of application-layer protocols send during the Application-Layer Protocol Negotiation. */ public List getApplicationLayerProtocols() { - return applicationLayerProtocols; + ClientSSLOptions o = getSslOptions(); + return o != null ? o.getApplicationLayerProtocols() : null; } /** @@ -383,7 +338,7 @@ public List getApplicationLayerProtocols() { * @return a reference to this, so the API can be used fluently */ public NetClientOptions setApplicationLayerProtocols(List protocols) { - this.applicationLayerProtocols = protocols; + getOrCreateSSLOptions().setApplicationLayerProtocols(protocols); return this; } diff --git a/src/main/java/io/vertx/core/net/NetServer.java b/src/main/java/io/vertx/core/net/NetServer.java index 6f4131d0067..f5d3fe918b6 100644 --- a/src/main/java/io/vertx/core/net/NetServer.java +++ b/src/main/java/io/vertx/core/net/NetServer.java @@ -119,13 +119,39 @@ default Future listen(int port) { int actualPort(); /** - * Update the server SSL options. + *

Update the server with new SSL {@code options}, the update happens if the options object is valid and different + * from the existing options object. * - * Update only happens if the SSL options is valid. + *

The boolean succeeded future result indicates whether the update occurred. * * @param options the new SSL options * @return a future signaling the update success */ - Future updateSSLOptions(SSLOptions options); + default Future updateSSLOptions(ServerSSLOptions options) { + return updateSSLOptions(options, false); + } + + /** + *

Update the server with new SSL {@code options}, the update happens if the options object is valid and different + * from the existing options object. + * + *

The {@code options} object is compared using its {@code equals} method against the existing options to prevent + * an update when the objects are equals since loading options can be costly, this can happen for share TCP servers. + * When object are equals, setting {@code force} to {@code true} forces the update. + * + *

The boolean succeeded future result indicates whether the update occurred. + * + * @param options the new SSL options + * @param force force the update when options are equals + * @return a future signaling the update success + */ + Future updateSSLOptions(ServerSSLOptions options, boolean force); + /** + * Update traffic shaping options {@code options}, the update happens if valid values are passed for traffic + * shaping options. This update happens synchronously and at best effort for rate update to take effect immediately. + * + * @param options the new traffic shaping options + */ + void updateTrafficShapingOptions(TrafficShapingOptions options); } diff --git a/src/main/java/io/vertx/core/net/NetServerOptions.java b/src/main/java/io/vertx/core/net/NetServerOptions.java index dbeca0af4ae..0cc58e038a2 100755 --- a/src/main/java/io/vertx/core/net/NetServerOptions.java +++ b/src/main/java/io/vertx/core/net/NetServerOptions.java @@ -13,6 +13,8 @@ import io.netty.handler.logging.ByteBufFormat; import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.annotations.GenIgnore; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.ClientAuth; import io.vertx.core.json.JsonObject; @@ -20,15 +22,13 @@ import java.util.Set; import java.util.concurrent.TimeUnit; -import static io.vertx.core.net.TrafficShapingOptions.DEFAULT_INBOUND_GLOBAL_BANDWIDTH_LIMIT; -import static io.vertx.core.net.TrafficShapingOptions.DEFAULT_OUTBOUND_GLOBAL_BANDWIDTH_LIMIT; - /** * Options for configuring a {@link io.vertx.core.net.NetServer}. * * @author Tim Fox */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public class NetServerOptions extends TCPSSLOptions { // Server specific HTTP stuff @@ -48,16 +48,6 @@ public class NetServerOptions extends TCPSSLOptions { */ public static final int DEFAULT_ACCEPT_BACKLOG = -1; - /** - * Default value of whether client auth is required (SSL/TLS) = No - */ - public static final ClientAuth DEFAULT_CLIENT_AUTH = ClientAuth.NONE; - - /** - * Default value of whether the server supports SNI = false - */ - public static final boolean DEFAULT_SNI = false; - /** * Default value of whether the server supports HA PROXY protocol = false */ @@ -81,8 +71,6 @@ public class NetServerOptions extends TCPSSLOptions { private int port; private String host; private int acceptBacklog; - private ClientAuth clientAuth; - private boolean sni; private boolean useProxyProtocol; private long proxyProtocolTimeout; private TimeUnit proxyProtocolTimeoutUnit; @@ -107,8 +95,6 @@ public NetServerOptions(NetServerOptions other) { this.port = other.getPort(); this.host = other.getHost(); this.acceptBacklog = other.getAcceptBacklog(); - this.clientAuth = other.getClientAuth(); - this.sni = other.isSni(); this.useProxyProtocol = other.isUseProxyProtocol(); this.proxyProtocolTimeout = other.proxyProtocolTimeout; this.proxyProtocolTimeoutUnit = other.getProxyProtocolTimeoutUnit() != null ? @@ -129,6 +115,15 @@ public NetServerOptions(JsonObject json) { NetServerOptionsConverter.fromJson(json, this); } + /** + * Copy these options. + * + * @return a copy of this + */ + public NetServerOptions copy() { + return new NetServerOptions(this); + } + /** * Convert to JSON * @@ -140,6 +135,17 @@ public JsonObject toJson() { return json; } + @GenIgnore + @Override + public ServerSSLOptions getSslOptions() { + return (ServerSSLOptions) super.getSslOptions(); + } + + @Override + protected ServerSSLOptions getOrCreateSSLOptions() { + return (ServerSSLOptions) super.getOrCreateSSLOptions(); + } + @Override public NetServerOptions setSendBufferSize(int sendBufferSize) { super.setSendBufferSize(sendBufferSize); @@ -230,60 +236,18 @@ public NetServerOptions setSslEngineOptions(SSLEngineOptions sslEngineOptions) { return this; } - @Override - public NetServerOptions setJdkSslEngineOptions(JdkSSLEngineOptions sslEngineOptions) { - return (NetServerOptions) super.setSslEngineOptions(sslEngineOptions); - } - - @Override - public NetServerOptions setOpenSslEngineOptions(OpenSSLEngineOptions sslEngineOptions) { - return (NetServerOptions) super.setSslEngineOptions(sslEngineOptions); - } - @Override public NetServerOptions setKeyCertOptions(KeyCertOptions options) { super.setKeyCertOptions(options); return this; } - @Override - public NetServerOptions setKeyStoreOptions(JksOptions options) { - super.setKeyStoreOptions(options); - return this; - } - - @Override - public NetServerOptions setPfxKeyCertOptions(PfxOptions options) { - return (NetServerOptions) super.setPfxKeyCertOptions(options); - } - - @Override - public NetServerOptions setPemKeyCertOptions(PemKeyCertOptions options) { - return (NetServerOptions) super.setPemKeyCertOptions(options); - } - @Override public NetServerOptions setTrustOptions(TrustOptions options) { super.setTrustOptions(options); return this; } - @Override - public NetServerOptions setTrustStoreOptions(JksOptions options) { - super.setTrustStoreOptions(options); - return this; - } - - @Override - public NetServerOptions setPfxTrustOptions(PfxOptions options) { - return (NetServerOptions) super.setPfxTrustOptions(options); - } - - @Override - public NetServerOptions setPemTrustOptions(PemTrustOptions options) { - return (NetServerOptions) super.setPemTrustOptions(options); - } - @Override public NetServerOptions addEnabledCipherSuite(String suite) { super.addEnabledCipherSuite(suite); @@ -406,7 +370,8 @@ public NetServerOptions setHost(String host) { } public ClientAuth getClientAuth() { - return clientAuth; + ServerSSLOptions o = getSslOptions(); + return o != null ? o.getClientAuth() : ServerSSLOptions.DEFAULT_CLIENT_AUTH; } /** @@ -418,7 +383,7 @@ public ClientAuth getClientAuth() { * @return a reference to this, so the API can be used fluently */ public NetServerOptions setClientAuth(ClientAuth clientAuth) { - this.clientAuth = clientAuth; + getOrCreateSSLOptions().setClientAuth(clientAuth); return this; } @@ -436,7 +401,8 @@ public NetServerOptions setActivityLogDataFormat(ByteBufFormat activityLogDataFo * @return whether the server supports Server Name Indication */ public boolean isSni() { - return sni; + ServerSSLOptions o = getSslOptions(); + return o != null ? o.isSni() : ServerSSLOptions.DEFAULT_SNI; } /** @@ -445,7 +411,7 @@ public boolean isSni() { * @return a reference to this, so the API can be used fluently */ public NetServerOptions setSni(boolean sni) { - this.sni = sni; + getOrCreateSSLOptions().setSni(sni); return this; } @@ -525,8 +491,6 @@ private void init() { this.port = DEFAULT_PORT; this.host = DEFAULT_HOST; this.acceptBacklog = DEFAULT_ACCEPT_BACKLOG; - this.clientAuth = DEFAULT_CLIENT_AUTH; - this.sni = DEFAULT_SNI; this.useProxyProtocol = DEFAULT_USE_PROXY_PROTOCOL; this.proxyProtocolTimeout = DEFAULT_PROXY_PROTOCOL_TIMEOUT; this.proxyProtocolTimeoutUnit = DEFAULT_PROXY_PROTOCOL_TIMEOUT_TIME_UNIT; diff --git a/src/main/java/io/vertx/core/net/NetSocket.java b/src/main/java/io/vertx/core/net/NetSocket.java index b48b72edb48..5dd5f585fcd 100644 --- a/src/main/java/io/vertx/core/net/NetSocket.java +++ b/src/main/java/io/vertx/core/net/NetSocket.java @@ -189,12 +189,25 @@ default Future sendFile(String filename, long offset) { @Fluent NetSocket closeHandler(@Nullable Handler handler); + /** + * Set a handler that will be called when the NetSocket is shutdown: the client or server will close the connection + * within a certain amount of time. This gives the opportunity to the handler to close the socket gracefully before + * the socket is closed. + * + * @param handler the handler notified with the remaining shutdown time in milliseconds + * @return a reference to this, so the API can be used fluently + */ + @Fluent + NetSocket shutdownHandler(@Nullable Handler handler); + /** * Upgrade channel to use SSL/TLS. Be aware that for this to work SSL must be configured. * * @return a future completed when the connection has been upgraded to SSL */ - Future upgradeToSsl(); + default Future upgradeToSsl() { + return upgradeToSsl((String) null); + } /** * Upgrade channel to use SSL/TLS. Be aware that for this to work SSL must be configured. @@ -204,6 +217,25 @@ default Future sendFile(String filename, long offset) { */ Future upgradeToSsl(String serverName); + /** + * Upgrade channel to use SSL/TLS. Be aware that for this to work SSL must be configured. + * + * @param sslOptions the SSL options + * @param serverName the server name + * @return a future completed when the connection has been upgraded to SSL + */ + Future upgradeToSsl(SSLOptions sslOptions, String serverName); + + /** + * Upgrade channel to use SSL/TLS. Be aware that for this to work SSL must be configured. + * + * @param sslOptions the SSL options + * @return a future completed when the connection has been upgraded to SSL + */ + default Future upgradeToSsl(SSLOptions sslOptions) { + return upgradeToSsl(sslOptions, null); + } + /** * @return true if this {@link io.vertx.core.net.NetSocket} is encrypted via SSL/TLS. */ diff --git a/src/main/java/io/vertx/core/net/NetworkOptions.java b/src/main/java/io/vertx/core/net/NetworkOptions.java index 2ea891ce925..855db3412f6 100644 --- a/src/main/java/io/vertx/core/net/NetworkOptions.java +++ b/src/main/java/io/vertx/core/net/NetworkOptions.java @@ -13,6 +13,7 @@ import io.vertx.codegen.annotations.DataObject; import io.vertx.codegen.annotations.Unstable; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.impl.Arguments; import io.vertx.core.json.JsonObject; import io.netty.handler.logging.ByteBufFormat; @@ -21,7 +22,8 @@ /** * @author Tim Fox */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public abstract class NetworkOptions { /** diff --git a/src/main/java/io/vertx/core/net/OpenSSLEngineOptions.java b/src/main/java/io/vertx/core/net/OpenSSLEngineOptions.java index 5d2c88e6ab9..0c906c4deeb 100644 --- a/src/main/java/io/vertx/core/net/OpenSSLEngineOptions.java +++ b/src/main/java/io/vertx/core/net/OpenSSLEngineOptions.java @@ -14,6 +14,7 @@ import io.netty.handler.ssl.OpenSsl; import io.netty.handler.ssl.SslProvider; import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.json.JsonObject; import io.vertx.core.spi.tls.DefaultSslContextFactory; import io.vertx.core.spi.tls.SslContextFactory; @@ -23,7 +24,8 @@ * * @author Julien Viet */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public class OpenSSLEngineOptions extends SSLEngineOptions { /** @@ -37,7 +39,7 @@ public static boolean isAvailable() { * @return when alpn support is available via OpenSSL engine */ public static boolean isAlpnAvailable() { - return OpenSsl.isAlpnSupported(); + return SslProvider.isAlpnSupported(SslProvider.OPENSSL); } /** diff --git a/src/main/java/io/vertx/core/net/PemKeyCertOptions.java b/src/main/java/io/vertx/core/net/PemKeyCertOptions.java index d3e1e7c266d..ab252447c38 100644 --- a/src/main/java/io/vertx/core/net/PemKeyCertOptions.java +++ b/src/main/java/io/vertx/core/net/PemKeyCertOptions.java @@ -13,6 +13,7 @@ import io.vertx.codegen.annotations.DataObject; import io.vertx.codegen.annotations.GenIgnore; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; import io.vertx.core.impl.Arguments; @@ -94,7 +95,8 @@ * @author Julien Viet * @author Tim Fox */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public class PemKeyCertOptions implements KeyCertOptions { private KeyStoreHelper helper; @@ -400,6 +402,15 @@ public boolean equals(Object obj) { return false; } + @Override + public int hashCode() { + int hashCode = Objects.hashCode(keyPaths); + hashCode = 31 * hashCode + Objects.hashCode(keyValues); + hashCode = 31 * hashCode + Objects.hashCode(certPaths); + hashCode = 31 * hashCode + Objects.hashCode(certValues); + return hashCode; + } + @Override public PemKeyCertOptions copy() { return new PemKeyCertOptions(this); diff --git a/src/main/java/io/vertx/core/net/PemTrustOptions.java b/src/main/java/io/vertx/core/net/PemTrustOptions.java index 08fde5712b3..3f154fda45b 100644 --- a/src/main/java/io/vertx/core/net/PemTrustOptions.java +++ b/src/main/java/io/vertx/core/net/PemTrustOptions.java @@ -12,6 +12,7 @@ package io.vertx.core.net; import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; import io.vertx.core.impl.Arguments; @@ -62,7 +63,8 @@ * @author Julien Viet * @author Tim Fox */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public class PemTrustOptions implements TrustOptions, Cloneable { private KeyStoreHelper helper; @@ -187,6 +189,13 @@ public boolean equals(Object obj) { return false; } + @Override + public int hashCode() { + int hashCode = Objects.hashCode(certPaths); + hashCode = 31 * hashCode + Objects.hashCode(certValues); + return hashCode; + } + @Override public PemTrustOptions copy() { return new PemTrustOptions(this); diff --git a/src/main/java/io/vertx/core/net/PfxOptions.java b/src/main/java/io/vertx/core/net/PfxOptions.java index 4c7090ff4dd..06bb9b52249 100644 --- a/src/main/java/io/vertx/core/net/PfxOptions.java +++ b/src/main/java/io/vertx/core/net/PfxOptions.java @@ -12,6 +12,7 @@ package io.vertx.core.net; import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.buffer.Buffer; import io.vertx.core.json.JsonObject; @@ -21,7 +22,8 @@ * @author Julien Viet * @author Tim Fox */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public class PfxOptions extends KeyStoreOptionsBase { /** diff --git a/src/main/java/io/vertx/core/net/ProxyOptions.java b/src/main/java/io/vertx/core/net/ProxyOptions.java index fedd84aec99..bced20f8092 100644 --- a/src/main/java/io/vertx/core/net/ProxyOptions.java +++ b/src/main/java/io/vertx/core/net/ProxyOptions.java @@ -14,6 +14,7 @@ import java.util.Objects; import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.json.JsonObject; /** @@ -21,7 +22,8 @@ * * @author Alexander Lehmann */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public class ProxyOptions { /** @@ -32,7 +34,7 @@ public class ProxyOptions { /** * The default port for proxy connect = 3128 * - * 3128 is the default port for e.g. Squid + * 3128 is the default port for Squid */ public static final int DEFAULT_PORT = 3128; diff --git a/src/main/java/io/vertx/core/net/SSLOptions.java b/src/main/java/io/vertx/core/net/SSLOptions.java index 826519d19f7..e2123d72971 100644 --- a/src/main/java/io/vertx/core/net/SSLOptions.java +++ b/src/main/java/io/vertx/core/net/SSLOptions.java @@ -12,6 +12,7 @@ import io.vertx.codegen.annotations.DataObject; import io.vertx.codegen.annotations.GenIgnore; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.buffer.Buffer; import io.vertx.core.json.JsonObject; @@ -25,11 +26,12 @@ import java.util.concurrent.TimeUnit; /** - * SSL options + * Client/Server SSL options. * * @author Tim Fox */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public class SSLOptions { /** @@ -60,21 +62,12 @@ public class SSLOptions { private TimeUnit sslHandshakeTimeoutUnit; private KeyCertOptions keyCertOptions; private TrustOptions trustOptions; - private Set enabledCipherSuites; - private ArrayList crlPaths; - private ArrayList crlValues; + Set enabledCipherSuites; + List crlPaths; + List crlValues; private boolean useAlpn; private Set enabledSecureTransportProtocols; - - /** - * Create options from JSON - * - * @param json the JSON - */ - public SSLOptions(JsonObject json) { - this(); - SSLOptionsConverter.fromJson(json ,this); - } + private List applicationLayerProtocols; /** * Default constructor @@ -98,10 +91,21 @@ public SSLOptions(SSLOptions other) { this.crlValues = new ArrayList<>(other.getCrlValues()); this.useAlpn = other.useAlpn; this.enabledSecureTransportProtocols = other.getEnabledSecureTransportProtocols() == null ? new LinkedHashSet<>() : new LinkedHashSet<>(other.getEnabledSecureTransportProtocols()); + this.applicationLayerProtocols = other.getApplicationLayerProtocols() != null ? new ArrayList<>(other.getApplicationLayerProtocols()) : null; + } + + /** + * Create options from JSON + * + * @param json the JSON + */ + public SSLOptions(JsonObject json) { + this(); + SSLOptionsConverter.fromJson(json ,this); } - private void init() { + protected void init() { sslHandshakeTimeout = DEFAULT_SSL_HANDSHAKE_TIMEOUT; sslHandshakeTimeoutUnit = DEFAULT_SSL_HANDSHAKE_TIMEOUT_TIME_UNIT; enabledCipherSuites = new LinkedHashSet<>(); @@ -109,6 +113,11 @@ private void init() { crlValues = new ArrayList<>(); useAlpn = DEFAULT_USE_ALPN; enabledSecureTransportProtocols = new LinkedHashSet<>(DEFAULT_ENABLED_SECURE_TRANSPORT_PROTOCOLS); + applicationLayerProtocols = null; + } + + public SSLOptions copy() { + return new SSLOptions(this); } /** @@ -324,6 +333,24 @@ public SSLOptions removeEnabledSecureTransportProtocol(String protocol) { return this; } + /** + * @return the list of application-layer protocols send during the Application-Layer Protocol Negotiation. + */ + public List getApplicationLayerProtocols() { + return applicationLayerProtocols; + } + + /** + * Set the list of application-layer protocols to provide to the server during the Application-Layer Protocol Negotiation. + * + * @param protocols the protocols + * @return a reference to this, so the API can be used fluently + */ + public SSLOptions setApplicationLayerProtocols(List protocols) { + this.applicationLayerProtocols = protocols; + return this; + } + @Override public boolean equals(Object obj) { if (obj == this) { @@ -331,7 +358,7 @@ public boolean equals(Object obj) { } if (obj instanceof SSLOptions) { SSLOptions that = (SSLOptions) obj; - return sslHandshakeTimeoutUnit.toNanos(sslHandshakeTimeout) == that.sslHandshakeTimeoutUnit.toNanos(sslHandshakeTimeout) && + return sslHandshakeTimeoutUnit.toNanos(sslHandshakeTimeout) == that.sslHandshakeTimeoutUnit.toNanos(that.sslHandshakeTimeout) && Objects.equals(keyCertOptions, that.keyCertOptions) && Objects.equals(trustOptions, that.trustOptions) && Objects.equals(enabledCipherSuites, that.enabledCipherSuites) && @@ -343,6 +370,11 @@ public boolean equals(Object obj) { return false; } + @Override + public int hashCode() { + return Objects.hash(sslHandshakeTimeoutUnit.toNanos(sslHandshakeTimeout), keyCertOptions, trustOptions, enabledCipherSuites, crlPaths, crlValues, useAlpn, enabledSecureTransportProtocols); + } + /** * Convert to JSON * diff --git a/src/main/java/io/vertx/core/net/ServerOptionsBase.java b/src/main/java/io/vertx/core/net/ServerOptionsBase.java deleted file mode 100644 index ee235bdfc0e..00000000000 --- a/src/main/java/io/vertx/core/net/ServerOptionsBase.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.net; - -/** - * @author Tim Fox - */ -public class ServerOptionsBase extends TCPSSLOptions { -} diff --git a/src/main/java/io/vertx/core/net/ServerSSLOptions.java b/src/main/java/io/vertx/core/net/ServerSSLOptions.java new file mode 100644 index 00000000000..6e2edc36ab1 --- /dev/null +++ b/src/main/java/io/vertx/core/net/ServerSSLOptions.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.net; + +import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.ClientAuth; +import io.vertx.core.json.JsonObject; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * Server SSL options. + */ +@DataObject +@JsonGen(publicConverter = false) +public class ServerSSLOptions extends SSLOptions { + + /** + * Default value of whether client auth is required (SSL/TLS) = No + */ + public static final ClientAuth DEFAULT_CLIENT_AUTH = ClientAuth.NONE; + + /** + * Default value of whether the server supports SNI = false + */ + public static final boolean DEFAULT_SNI = false; + + private ClientAuth clientAuth; + private boolean sni; + + /** + * Default constructor + */ + public ServerSSLOptions() { + super(); + } + + /** + * Copy constructor + * + * @param other the options to copy + */ + public ServerSSLOptions(ServerSSLOptions other) { + super(other); + clientAuth = other.clientAuth; + sni = other.sni; + } + + /** + * Create options from JSON + * + * @param json the JSON + */ + public ServerSSLOptions(JsonObject json) { + super(json); + ServerSSLOptionsConverter.fromJson(json, this); + } + + @Override + protected void init() { + super.init(); + this.clientAuth = DEFAULT_CLIENT_AUTH; + this.sni = DEFAULT_SNI; + } + + public ServerSSLOptions copy() { + return new ServerSSLOptions(this); + } + + public ClientAuth getClientAuth() { + return clientAuth; + } + + /** + * Set whether client auth is required + * + * @param clientAuth One of "NONE, REQUEST, REQUIRED". If it's set to "REQUIRED" then server will require the + * SSL cert to be presented otherwise it won't accept the request. If it's set to "REQUEST" then + * it won't mandate the certificate to be presented, basically make it optional. + * @return a reference to this, so the API can be used fluently + */ + public ServerSSLOptions setClientAuth(ClientAuth clientAuth) { + this.clientAuth = clientAuth; + return this; + } + + /** + * @return whether the server supports Server Name Indication + */ + public boolean isSni() { + return sni; + } + + /** + * Set whether the server supports Server Name Indiciation + * + * @return a reference to this, so the API can be used fluently + */ + public ServerSSLOptions setSni(boolean sni) { + this.sni = sni; + return this; + } + + @Override + public ServerSSLOptions setKeyCertOptions(KeyCertOptions options) { + return (ServerSSLOptions) super.setKeyCertOptions(options); + } + + @Override + public ServerSSLOptions setTrustOptions(TrustOptions options) { + return (ServerSSLOptions) super.setTrustOptions(options); + } + + @Override + public ServerSSLOptions setUseAlpn(boolean useAlpn) { + return (ServerSSLOptions) super.setUseAlpn(useAlpn); + } + + @Override + public ServerSSLOptions setSslHandshakeTimeout(long sslHandshakeTimeout) { + return (ServerSSLOptions) super.setSslHandshakeTimeout(sslHandshakeTimeout); + } + + @Override + public ServerSSLOptions setSslHandshakeTimeoutUnit(TimeUnit sslHandshakeTimeoutUnit) { + return (ServerSSLOptions) super.setSslHandshakeTimeoutUnit(sslHandshakeTimeoutUnit); + } + + @Override + public ServerSSLOptions setEnabledSecureTransportProtocols(Set enabledSecureTransportProtocols) { + return (ServerSSLOptions) super.setEnabledSecureTransportProtocols(enabledSecureTransportProtocols); + } + + @Override + public ServerSSLOptions setApplicationLayerProtocols(List protocols) { + return (ServerSSLOptions) super.setApplicationLayerProtocols(protocols); + } + + @Override + public ServerSSLOptions addEnabledCipherSuite(String suite) { + return (ServerSSLOptions) super.addEnabledCipherSuite(suite); + } + + @Override + public ServerSSLOptions addCrlPath(String crlPath) throws NullPointerException { + return (ServerSSLOptions) super.addCrlPath(crlPath); + } + + @Override + public ServerSSLOptions addCrlValue(Buffer crlValue) throws NullPointerException { + return (ServerSSLOptions) super.addCrlValue(crlValue); + } + + @Override + public ServerSSLOptions addEnabledSecureTransportProtocol(String protocol) { + return (ServerSSLOptions) super.addEnabledSecureTransportProtocol(protocol); + } + + /** + * Convert to JSON + * + * @return the JSON + */ + public JsonObject toJson() { + JsonObject json = super.toJson(); + ServerSSLOptionsConverter.toJson(this, json); + return json; + } +} diff --git a/src/main/java/io/vertx/core/net/SocketAddress.java b/src/main/java/io/vertx/core/net/SocketAddress.java index 4c6640393a6..fe2fa5a41dc 100644 --- a/src/main/java/io/vertx/core/net/SocketAddress.java +++ b/src/main/java/io/vertx/core/net/SocketAddress.java @@ -12,9 +12,10 @@ package io.vertx.core.net; import io.vertx.codegen.annotations.CacheReturn; +import io.vertx.codegen.annotations.DataObject; import io.vertx.codegen.annotations.GenIgnore; -import io.vertx.codegen.annotations.VertxGen; import io.vertx.core.impl.Arguments; +import io.vertx.core.json.JsonObject; import io.vertx.core.net.impl.SocketAddressImpl; import java.net.InetSocketAddress; @@ -27,9 +28,26 @@ * * @author Tim Fox */ -@VertxGen +@DataObject public interface SocketAddress extends Address { + static SocketAddress fromJson(JsonObject json) { + Integer port = json.getInteger("port"); + String host = json.getString("host"); + if (host != null && port != null) { + if (port >= 0) { + return inetSocketAddress(port, host); + } else { + return sharedRandomPort(-port, host); + } + } + String path = json.getString("path"); + if (path != null) { + return domainSocketAddress(path); + } + return null; + } + /** * Create an inet socket address that binds to a shared random port identified by {@code id}. *
@@ -142,4 +160,13 @@ static SocketAddress inetSocketAddress(InetSocketAddress address) { @CacheReturn boolean isDomainSocket(); + default JsonObject toJson() { + if (isInetSocket()) { + return new JsonObject().put("host", host()).put("port", port()); + } else if (isDomainSocket()) { + return new JsonObject().put("path", path()); + } else { + throw new IllegalStateException(); + } + } } diff --git a/src/main/java/io/vertx/core/net/TCPSSLOptions.java b/src/main/java/io/vertx/core/net/TCPSSLOptions.java index b982c95e26c..994718ceecd 100755 --- a/src/main/java/io/vertx/core/net/TCPSSLOptions.java +++ b/src/main/java/io/vertx/core/net/TCPSSLOptions.java @@ -13,6 +13,7 @@ import io.vertx.codegen.annotations.DataObject; import io.vertx.codegen.annotations.GenIgnore; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.buffer.Buffer; import io.vertx.core.json.JsonObject; import io.netty.handler.logging.ByteBufFormat; @@ -25,7 +26,8 @@ * * @author Tim Fox */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public abstract class TCPSSLOptions extends NetworkOptions { /** @@ -68,21 +70,11 @@ public abstract class TCPSSLOptions extends NetworkOptions { */ public static final int DEFAULT_WRITE_IDLE_TIMEOUT = 0; - /** - * See {@link SSLOptions#DEFAULT_USE_ALPN} - */ - public static final boolean DEFAULT_USE_ALPN = SSLOptions.DEFAULT_USE_ALPN; - /** * The default SSL engine options = null (autoguess) */ public static final SSLEngineOptions DEFAULT_SSL_ENGINE = null; - /** - * See {@link SSLOptions#DEFAULT_ENABLED_SECURE_TRANSPORT_PROTOCOLS} - */ - public static final List DEFAULT_ENABLED_SECURE_TRANSPORT_PROTOCOLS = SSLOptions.DEFAULT_ENABLED_SECURE_TRANSPORT_PROTOCOLS; - /** * The default TCP_FASTOPEN value = false */ @@ -105,16 +97,6 @@ public abstract class TCPSSLOptions extends NetworkOptions { */ public static final int DEFAULT_TCP_USER_TIMEOUT = 0; - /** - * See {@link SSLOptions#DEFAULT_SSL_HANDSHAKE_TIMEOUT} - */ - public static final long DEFAULT_SSL_HANDSHAKE_TIMEOUT = SSLOptions.DEFAULT_SSL_HANDSHAKE_TIMEOUT; - - /** - * See {@link SSLOptions#DEFAULT_SSL_HANDSHAKE_TIMEOUT_TIME_UNIT} - */ - public static final TimeUnit DEFAULT_SSL_HANDSHAKE_TIMEOUT_TIME_UNIT = SSLOptions.DEFAULT_SSL_HANDSHAKE_TIMEOUT_TIME_UNIT; - private boolean tcpNoDelay; private boolean tcpKeepAlive; private int soLinger; @@ -130,6 +112,10 @@ public abstract class TCPSSLOptions extends NetworkOptions { private boolean tcpQuickAck; private int tcpUserTimeout; + private Set enabledCipherSuites; + private List crlPaths; + private List crlValues; + /** * Default constructor */ @@ -158,7 +144,16 @@ public TCPSSLOptions(TCPSSLOptions other) { this.tcpCork = other.isTcpCork(); this.tcpQuickAck = other.isTcpQuickAck(); this.tcpUserTimeout = other.getTcpUserTimeout(); - this.sslOptions = new SSLOptions(other.sslOptions); + + SSLOptions sslOptions = other.sslOptions; + if (sslOptions != null) { + this.sslOptions = sslOptions.copy(); + if (this.sslOptions != null) { + enabledCipherSuites = this.sslOptions.enabledCipherSuites; + crlPaths = this.sslOptions.crlPaths; + crlValues = this.sslOptions.crlValues; + } + } } /** @@ -170,6 +165,26 @@ public TCPSSLOptions(JsonObject json) { super(json); init(); TCPSSLOptionsConverter.fromJson(json ,this); + // Legacy + if (json.containsKey("pemKeyCertOptions")) { + setKeyCertOptions(new PemKeyCertOptions(json.getJsonObject("pemKeyCertOptions"))); + } else if (json.containsKey("keyStoreOptions")) { + setKeyCertOptions(new JksOptions(json.getJsonObject("keyStoreOptions"))); + } else if (json.containsKey("pfxKeyCertOptions")) { + setKeyCertOptions(new PfxOptions(json.getJsonObject("pfxKeyCertOptions"))); + } + if (json.containsKey("pemTrustOptions")) { + setTrustOptions(new PemTrustOptions(json.getJsonObject("pemTrustOptions"))); + } else if (json.containsKey("pfxTrustOptions")) { + setTrustOptions(new PfxOptions(json.getJsonObject("pfxTrustOptions"))); + } else if (json.containsKey("trustStoreOptions")) { + setTrustOptions(new JksOptions(json.getJsonObject("trustStoreOptions"))); + } + if (json.containsKey("jdkSslEngineOptions")) { + setSslEngineOptions(new JdkSSLEngineOptions(json.getJsonObject("jdkSslEngineOptions"))); + } else if (json.containsKey("openSslEngineOptions")) { + setSslEngineOptions(new OpenSSLEngineOptions(json.getJsonObject("openSslEngineOptions"))); + } } /** @@ -180,6 +195,34 @@ public TCPSSLOptions(JsonObject json) { public JsonObject toJson() { JsonObject json = super.toJson(); TCPSSLOptionsConverter.toJson(this, json); + if (sslOptions != null) { + KeyCertOptions keyCertOptions = sslOptions.getKeyCertOptions(); + if (keyCertOptions != null) { + if (keyCertOptions instanceof PemKeyCertOptions) { + json.put("pemKeyCertOptions", ((PemKeyCertOptions) keyCertOptions).toJson()); + } else if (keyCertOptions instanceof JksOptions) { + json.put("keyStoreOptions", ((JksOptions) keyCertOptions).toJson()); + } else if (keyCertOptions instanceof PfxOptions) { + json.put("pfxKeyCertOptions", ((PfxOptions) keyCertOptions).toJson()); + } + } + TrustOptions trustOptions = sslOptions.getTrustOptions(); + if (trustOptions instanceof PemTrustOptions) { + json.put("pemTrustOptions", ((PemTrustOptions) trustOptions).toJson()); + } else if (trustOptions instanceof PfxOptions) { + json.put("pfxTrustOptions", ((PfxOptions) trustOptions).toJson()); + } else if (trustOptions instanceof JksOptions) { + json.put("trustStoreOptions", ((JksOptions) trustOptions).toJson()); + } + } + SSLEngineOptions engineOptions = sslEngineOptions; + if (engineOptions != null) { + if (engineOptions instanceof JdkSSLEngineOptions) { + json.put("jdkSslEngineOptions", ((JdkSSLEngineOptions) engineOptions).toJson()); + } else if (engineOptions instanceof OpenSSLEngineOptions) { + json.put("openSslEngineOptions", ((OpenSSLEngineOptions) engineOptions).toJson()); + } + } return json; } @@ -197,7 +240,30 @@ private void init() { tcpCork = DEFAULT_TCP_CORK; tcpQuickAck = DEFAULT_TCP_QUICKACK; tcpUserTimeout = DEFAULT_TCP_USER_TIMEOUT; - sslOptions = new SSLOptions(); + sslOptions = null; + } + + protected SSLOptions getOrCreateSSLOptions() { + if (sslOptions == null) { + sslOptions = this instanceof ClientOptionsBase ? new ClientSSLOptions() : new ServerSSLOptions(); + // Necessary hacks because we return lazy created collections so we need to care about that + if (enabledCipherSuites != null) { + sslOptions.enabledCipherSuites = enabledCipherSuites; + } else { + enabledCipherSuites = sslOptions.enabledCipherSuites; + } + if (crlPaths != null) { + sslOptions.crlPaths = crlPaths; + } else { + crlPaths = sslOptions.crlPaths; + } + if (crlValues != null) { + sslOptions.crlValues = crlValues; + } else { + crlValues = sslOptions.crlValues; + } + } + return sslOptions; } @GenIgnore @@ -377,7 +443,8 @@ public TCPSSLOptions setSsl(boolean ssl) { */ @GenIgnore public KeyCertOptions getKeyCertOptions() { - return sslOptions.getKeyCertOptions(); + SSLOptions o = sslOptions; + return o != null ? o.getKeyCertOptions() : null; } /** @@ -388,72 +455,16 @@ public KeyCertOptions getKeyCertOptions() { */ @GenIgnore public TCPSSLOptions setKeyCertOptions(KeyCertOptions options) { - sslOptions.setKeyCertOptions(options); + getOrCreateSSLOptions().setKeyCertOptions(options); return this; } - /** - * Get the key/cert options in jks format, aka Java keystore. - * - * @return the key/cert options in jks format, aka Java keystore. - */ - public JksOptions getKeyStoreOptions() { - KeyCertOptions keyCertOptions = sslOptions.getKeyCertOptions(); - return keyCertOptions instanceof JksOptions ? (JksOptions) keyCertOptions : null; - } - - /** - * Set the key/cert options in jks format, aka Java keystore. - * @param options the key store in jks format - * @return a reference to this, so the API can be used fluently - */ - public TCPSSLOptions setKeyStoreOptions(JksOptions options) { - return setKeyCertOptions(options); - } - - /** - * Get the key/cert options in pfx format. - * - * @return the key/cert options in pfx format. - */ - public PfxOptions getPfxKeyCertOptions() { - KeyCertOptions keyCertOptions = sslOptions.getKeyCertOptions(); - return keyCertOptions instanceof PfxOptions ? (PfxOptions) keyCertOptions : null; - } - - /** - * Set the key/cert options in pfx format. - * @param options the key cert options in pfx format - * @return a reference to this, so the API can be used fluently - */ - public TCPSSLOptions setPfxKeyCertOptions(PfxOptions options) { - return setKeyCertOptions(options); - } - - /** - * Get the key/cert store options in pem format. - * - * @return the key/cert store options in pem format. - */ - public PemKeyCertOptions getPemKeyCertOptions() { - KeyCertOptions keyCertOptions = sslOptions.getKeyCertOptions(); - return keyCertOptions instanceof PemKeyCertOptions ? (PemKeyCertOptions) keyCertOptions : null; - } - - /** - * Set the key/cert store options in pem format. - * @param options the options in pem format - * @return a reference to this, so the API can be used fluently - */ - public TCPSSLOptions setPemKeyCertOptions(PemKeyCertOptions options) { - return setKeyCertOptions(options); - } - /** * @return the trust options */ public TrustOptions getTrustOptions() { - return sslOptions.getTrustOptions(); + SSLOptions o = sslOptions; + return o != null ? o.getTrustOptions() : null; } /** @@ -462,67 +473,10 @@ public TrustOptions getTrustOptions() { * @return a reference to this, so the API can be used fluently */ public TCPSSLOptions setTrustOptions(TrustOptions options) { - sslOptions.setTrustOptions(options); + getOrCreateSSLOptions().setTrustOptions(options); return this; } - /** - * Get the trust options in jks format, aka Java truststore - * - * @return the trust options in jks format, aka Java truststore - */ - public JksOptions getTrustStoreOptions() { - TrustOptions trustOptions = sslOptions.getTrustOptions(); - return trustOptions instanceof JksOptions ? (JksOptions) trustOptions : null; - } - - /** - * Set the trust options in jks format, aka Java truststore - * @param options the trust options in jks format - * @return a reference to this, so the API can be used fluently - */ - public TCPSSLOptions setTrustStoreOptions(JksOptions options) { - return setTrustOptions(options); - } - - /** - * Get the trust options in pfx format - * - * @return the trust options in pfx format - */ - public PfxOptions getPfxTrustOptions() { - TrustOptions trustOptions = sslOptions.getTrustOptions(); - return trustOptions instanceof PfxOptions ? (PfxOptions) trustOptions : null; - } - - /** - * Set the trust options in pfx format - * @param options the trust options in pfx format - * @return a reference to this, so the API can be used fluently - */ - public TCPSSLOptions setPfxTrustOptions(PfxOptions options) { - return setTrustOptions(options); - } - - /** - * Get the trust options in pem format - * - * @return the trust options in pem format - */ - public PemTrustOptions getPemTrustOptions() { - TrustOptions trustOptions = sslOptions.getTrustOptions(); - return trustOptions instanceof PemTrustOptions ? (PemTrustOptions) trustOptions : null; - } - - /** - * Set the trust options in pem format - * @param options the trust options in pem format - * @return a reference to this, so the API can be used fluently - */ - public TCPSSLOptions setPemTrustOptions(PemTrustOptions options) { - return setTrustOptions(options); - } - /** * Add an enabled cipher suite, appended to the ordered suites. * @@ -531,7 +485,7 @@ public TCPSSLOptions setPemTrustOptions(PemTrustOptions options) { * @see #getEnabledCipherSuites() */ public TCPSSLOptions addEnabledCipherSuite(String suite) { - sslOptions.addEnabledCipherSuite(suite); + getOrCreateSSLOptions().addEnabledCipherSuite(suite); return this; } @@ -542,7 +496,7 @@ public TCPSSLOptions addEnabledCipherSuite(String suite) { * @return a reference to this, so the API can be used fluently */ public TCPSSLOptions removeEnabledCipherSuite(String suite) { - sslOptions.removeEnabledCipherSuite(suite); + getOrCreateSSLOptions().removeEnabledCipherSuite(suite); return this; } @@ -557,7 +511,10 @@ public TCPSSLOptions removeEnabledCipherSuite(String suite) { * @return the enabled cipher suites */ public Set getEnabledCipherSuites() { - return sslOptions.getEnabledCipherSuites(); + if (enabledCipherSuites == null) { + enabledCipherSuites = new LinkedHashSet<>(); + } + return enabledCipherSuites; } /** @@ -565,7 +522,10 @@ public Set getEnabledCipherSuites() { * @return the CRL (Certificate revocation list) paths */ public List getCrlPaths() { - return sslOptions.getCrlPaths(); + if (crlPaths == null) { + crlPaths = new ArrayList<>(); + } + return crlPaths; } /** @@ -575,7 +535,7 @@ public List getCrlPaths() { * @throws NullPointerException */ public TCPSSLOptions addCrlPath(String crlPath) throws NullPointerException { - sslOptions.addCrlPath(crlPath); + getOrCreateSSLOptions().addCrlPath(crlPath); return this; } @@ -585,7 +545,10 @@ public TCPSSLOptions addCrlPath(String crlPath) throws NullPointerException { * @return the list of values */ public List getCrlValues() { - return sslOptions.getCrlValues(); + if (crlValues == null) { + crlValues = new ArrayList<>(); + } + return crlValues; } /** @@ -596,7 +559,7 @@ public List getCrlValues() { * @throws NullPointerException */ public TCPSSLOptions addCrlValue(Buffer crlValue) throws NullPointerException { - sslOptions.addCrlValue(crlValue); + getOrCreateSSLOptions().addCrlValue(crlValue); return this; } @@ -604,7 +567,8 @@ public TCPSSLOptions addCrlValue(Buffer crlValue) throws NullPointerException { * @return whether to use or not Application-Layer Protocol Negotiation */ public boolean isUseAlpn() { - return sslOptions.isUseAlpn(); + SSLOptions o = sslOptions; + return o != null && o.isUseAlpn(); } /** @@ -613,7 +577,7 @@ public boolean isUseAlpn() { * @param useAlpn true when Application-Layer Protocol Negotiation should be used */ public TCPSSLOptions setUseAlpn(boolean useAlpn) { - sslOptions.setUseAlpn(useAlpn); + getOrCreateSSLOptions().setUseAlpn(useAlpn); return this; } @@ -635,22 +599,6 @@ public TCPSSLOptions setSslEngineOptions(SSLEngineOptions sslEngineOptions) { return this; } - public JdkSSLEngineOptions getJdkSslEngineOptions() { - return sslEngineOptions instanceof JdkSSLEngineOptions ? (JdkSSLEngineOptions) sslEngineOptions : null; - } - - public TCPSSLOptions setJdkSslEngineOptions(JdkSSLEngineOptions sslEngineOptions) { - return setSslEngineOptions(sslEngineOptions); - } - - public OpenSSLEngineOptions getOpenSslEngineOptions() { - return sslEngineOptions instanceof OpenSSLEngineOptions ? (OpenSSLEngineOptions) sslEngineOptions : null; - } - - public TCPSSLOptions setOpenSslEngineOptions(OpenSSLEngineOptions sslEngineOptions) { - return setSslEngineOptions(sslEngineOptions); - } - /** * Sets the list of enabled SSL/TLS protocols. * @@ -658,7 +606,7 @@ public TCPSSLOptions setOpenSslEngineOptions(OpenSSLEngineOptions sslEngineOptio * @return a reference to this, so the API can be used fluently */ public TCPSSLOptions setEnabledSecureTransportProtocols(Set enabledSecureTransportProtocols) { - sslOptions.setEnabledSecureTransportProtocols(enabledSecureTransportProtocols); + getOrCreateSSLOptions().setEnabledSecureTransportProtocols(enabledSecureTransportProtocols); return this; } @@ -669,7 +617,7 @@ public TCPSSLOptions setEnabledSecureTransportProtocols(Set enabledSecur * @return a reference to this, so the API can be used fluently */ public TCPSSLOptions addEnabledSecureTransportProtocol(String protocol) { - sslOptions.addEnabledSecureTransportProtocol(protocol); + getOrCreateSSLOptions().addEnabledSecureTransportProtocol(protocol); return this; } @@ -680,7 +628,7 @@ public TCPSSLOptions addEnabledSecureTransportProtocol(String protocol) { * @return a reference to this, so the API can be used fluently */ public TCPSSLOptions removeEnabledSecureTransportProtocol(String protocol) { - sslOptions.removeEnabledSecureTransportProtocol(protocol); + getOrCreateSSLOptions().removeEnabledSecureTransportProtocol(protocol); return this; } @@ -758,14 +706,16 @@ public TCPSSLOptions setTcpUserTimeout(int tcpUserTimeout) { * @return the enabled protocols */ public Set getEnabledSecureTransportProtocols() { - return sslOptions.getEnabledSecureTransportProtocols(); + SSLOptions o = sslOptions; + return o != null ? o.getEnabledSecureTransportProtocols() : new LinkedHashSet<>(SSLOptions.DEFAULT_ENABLED_SECURE_TRANSPORT_PROTOCOLS); } /** * @return the SSL handshake timeout, in time unit specified by {@link #getSslHandshakeTimeoutUnit()}. */ public long getSslHandshakeTimeout() { - return sslOptions.getSslHandshakeTimeout(); + SSLOptions o = sslOptions; + return o != null ? o.getSslHandshakeTimeout() : SSLOptions.DEFAULT_SSL_HANDSHAKE_TIMEOUT; } /** @@ -775,7 +725,7 @@ public long getSslHandshakeTimeout() { * @return a reference to this, so the API can be used fluently */ public TCPSSLOptions setSslHandshakeTimeout(long sslHandshakeTimeout) { - sslOptions.setSslHandshakeTimeout(sslHandshakeTimeout); + getOrCreateSSLOptions().setSslHandshakeTimeout(sslHandshakeTimeout); return this; } @@ -786,7 +736,7 @@ public TCPSSLOptions setSslHandshakeTimeout(long sslHandshakeTimeout) { * @return a reference to this, so the API can be used fluently */ public TCPSSLOptions setSslHandshakeTimeoutUnit(TimeUnit sslHandshakeTimeoutUnit) { - sslOptions.setSslHandshakeTimeoutUnit(sslHandshakeTimeoutUnit); + getOrCreateSSLOptions().setSslHandshakeTimeoutUnit(sslHandshakeTimeoutUnit); return this; } @@ -794,7 +744,8 @@ public TCPSSLOptions setSslHandshakeTimeoutUnit(TimeUnit sslHandshakeTimeoutUnit * @return the SSL handshake timeout unit. */ public TimeUnit getSslHandshakeTimeoutUnit() { - return sslOptions.getSslHandshakeTimeoutUnit(); + SSLOptions o = sslOptions; + return o != null ? o.getSslHandshakeTimeoutUnit() : SSLOptions.DEFAULT_SSL_HANDSHAKE_TIMEOUT_TIME_UNIT; } @Override @@ -831,5 +782,4 @@ public TCPSSLOptions setTrafficClass(int trafficClass) { public TCPSSLOptions setReusePort(boolean reusePort) { return (TCPSSLOptions) super.setReusePort(reusePort); } - } diff --git a/src/main/java/io/vertx/core/net/TrafficShapingOptions.java b/src/main/java/io/vertx/core/net/TrafficShapingOptions.java index 05fd99ec110..0ceb21f3c4f 100644 --- a/src/main/java/io/vertx/core/net/TrafficShapingOptions.java +++ b/src/main/java/io/vertx/core/net/TrafficShapingOptions.java @@ -11,20 +11,23 @@ package io.vertx.core.net; +import java.util.Objects; import java.util.concurrent.TimeUnit; import io.netty.util.internal.ObjectUtil; import io.vertx.codegen.annotations.DataObject; import io.vertx.codegen.annotations.Unstable; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.json.JsonObject; /** * Options describing how {@link io.netty.handler.traffic.GlobalTrafficShapingHandler} will handle traffic shaping. */ @Unstable -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public class TrafficShapingOptions { - /* + /** * Default inbound bandwidth limit in bytes/sec = 0 (0 implies unthrottled) */ public static final long DEFAULT_INBOUND_GLOBAL_BANDWIDTH_LIMIT = 0; @@ -34,6 +37,24 @@ public class TrafficShapingOptions { */ public static final long DEFAULT_OUTBOUND_GLOBAL_BANDWIDTH_LIMIT = 0; + /** + * Default peak outbound bandwidth limit in bytes/sec = 400 Mbps (aligns with netty's default in + * {@code io.netty.handler.traffic.AbstractTrafficShapingHandler#DEFAULT_MAX_SIZE}) + */ + public static final long DEFAULT_PEAK_OUTBOUND_GLOBAL_BANDWIDTH = 400 * 1024 * 1024L; + + /** + * Default check interval for stats = 1 second. The units are in milliseconds. (Aligns with netty's + * default in {@link io.netty.handler.traffic.AbstractTrafficShapingHandler#DEFAULT_CHECK_INTERVAL}) + */ + public static final long DEFAULT_CHECK_INTERVAL = TimeUnit.SECONDS.toMillis(1); + + /** + * Default max delay to wait = 15 seconds. The units are in milliseconds. (Aligns with netty's + * default in {@link io.netty.handler.traffic.AbstractTrafficShapingHandler#DEFAULT_MAX_TIME}) + */ + public static final long DEFAULT_MAX_TIME = TimeUnit.SECONDS.toMillis(15); + private long inboundGlobalBandwidth; private long outboundGlobalBandwidth; private long peakOutboundGlobalBandwidth; @@ -43,6 +64,13 @@ public class TrafficShapingOptions { private TimeUnit checkIntervalForStatsTimeUnit; public TrafficShapingOptions() { + inboundGlobalBandwidth = DEFAULT_INBOUND_GLOBAL_BANDWIDTH_LIMIT; + outboundGlobalBandwidth = DEFAULT_OUTBOUND_GLOBAL_BANDWIDTH_LIMIT; + peakOutboundGlobalBandwidth = DEFAULT_PEAK_OUTBOUND_GLOBAL_BANDWIDTH; + maxDelayToWait = DEFAULT_MAX_TIME; + maxDelayToWaitTimeUnit = TimeUnit.MILLISECONDS; + checkIntervalForStats = DEFAULT_CHECK_INTERVAL; + checkIntervalForStatsTimeUnit = TimeUnit.MILLISECONDS; } public TrafficShapingOptions(TrafficShapingOptions other) { @@ -51,6 +79,8 @@ public TrafficShapingOptions(TrafficShapingOptions other) { this.peakOutboundGlobalBandwidth = other.getPeakOutboundGlobalBandwidth(); this.maxDelayToWait = other.getMaxDelayToWait(); this.checkIntervalForStats = other.getCheckIntervalForStats(); + this.maxDelayToWaitTimeUnit = other.getMaxDelayToWaitTimeUnit(); + this.checkIntervalForStatsTimeUnit = other.getCheckIntervalForStatsTimeUnit(); } public TrafficShapingOptions(JsonObject json) { @@ -104,19 +134,19 @@ public TrafficShapingOptions setMaxDelayToWait(long maxDelayToWaitTime) { * @return a reference to this, so the API can be used fluently */ public TrafficShapingOptions setMaxDelayToWaitUnit(TimeUnit maxDelayToWaitTimeUnit) { - this.maxDelayToWaitTimeUnit = maxDelayToWaitTimeUnit; + this.maxDelayToWaitTimeUnit = Objects.requireNonNull(maxDelayToWaitTimeUnit, "maxDelayToWaitTimeUnit"); return this; } /** - * Set the delay between two computations of performances for channels or 0 if no stats are to be computed + * Set the delay between two computations of performances for channels * * @param checkIntervalForStats delay between two computations of performances * @return a reference to this, so the API can be used fluently */ public TrafficShapingOptions setCheckIntervalForStats(long checkIntervalForStats) { this.checkIntervalForStats = checkIntervalForStats; - ObjectUtil.checkPositive(this.checkIntervalForStats, "checkIntervalForStats"); + ObjectUtil.checkPositiveOrZero(this.checkIntervalForStats, "checkIntervalForStats"); return this; } @@ -127,7 +157,7 @@ public TrafficShapingOptions setCheckIntervalForStats(long checkIntervalForStats * @return a reference to this, so the API can be used fluently */ public TrafficShapingOptions setCheckIntervalForStatsTimeUnit(TimeUnit checkIntervalForStatsTimeUnit) { - this.maxDelayToWaitTimeUnit = maxDelayToWaitTimeUnit; + this.checkIntervalForStatsTimeUnit = Objects.requireNonNull(checkIntervalForStatsTimeUnit, "checkIntervalForStatsTimeUnit"); return this; } @@ -159,7 +189,7 @@ public long getOutboundGlobalBandwidth() { } /** - * @return max outbound bandwdith limit in bytes + * @return max outbound bandwidth limit in bytes */ public long getPeakOutboundGlobalBandwidth() { return peakOutboundGlobalBandwidth; diff --git a/src/main/java/io/vertx/core/net/TrustManagerFactoryOptions.java b/src/main/java/io/vertx/core/net/TrustManagerFactoryOptions.java index f76b0a2b228..f3cc750b74f 100644 --- a/src/main/java/io/vertx/core/net/TrustManagerFactoryOptions.java +++ b/src/main/java/io/vertx/core/net/TrustManagerFactoryOptions.java @@ -15,6 +15,7 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; +import java.util.Objects; import java.util.function.Function; /** @@ -84,4 +85,20 @@ public Function trustManagerMapper(Vertx vertx) { return serverName -> trustManagerFactory.getTrustManagers(); } + @Override + public int hashCode() { + return trustManagerFactory.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof TrustManagerFactoryOptions) { + TrustManagerFactoryOptions that = (TrustManagerFactoryOptions) obj; + return Objects.equals(trustManagerFactory, that.trustManagerFactory); + } + return false; + } } diff --git a/src/main/java/io/vertx/core/net/impl/ChannelProvider.java b/src/main/java/io/vertx/core/net/impl/ChannelProvider.java index 4bf9f61d648..528f5ca029a 100644 --- a/src/main/java/io/vertx/core/net/impl/ChannelProvider.java +++ b/src/main/java/io/vertx/core/net/impl/ChannelProvider.java @@ -22,6 +22,7 @@ import io.vertx.core.Handler; import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.VertxInternal; +import io.vertx.core.net.ClientSSLOptions; import io.vertx.core.net.ProxyOptions; import io.vertx.core.net.ProxyType; import io.vertx.core.net.SocketAddress; @@ -29,6 +30,7 @@ import javax.net.ssl.SSLHandshakeException; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; /** * The logic for connecting to an host, this implementations performs a connection @@ -84,13 +86,13 @@ public String applicationProtocol() { return applicationProtocol; } - public Future connect(SocketAddress remoteAddress, SocketAddress peerAddress, String serverName, boolean ssl, boolean useAlpn) { + public Future connect(SocketAddress remoteAddress, SocketAddress peerAddress, String serverName, boolean ssl, ClientSSLOptions sslOptions) { Promise p = context.nettyEventLoop().newPromise(); - connect(handler, remoteAddress, peerAddress, serverName, ssl, useAlpn, p); + connect(handler, remoteAddress, peerAddress, serverName, ssl, sslOptions, p); return p; } - private void connect(Handler handler, SocketAddress remoteAddress, SocketAddress peerAddress, String serverName, boolean ssl, boolean useAlpn, Promise p) { + private void connect(Handler handler, SocketAddress remoteAddress, SocketAddress peerAddress, String serverName, boolean ssl, ClientSSLOptions sslOptions, Promise p) { try { bootstrap.channelFactory(context.owner().transport().channelFactory(remoteAddress.isDomainSocket())); } catch (Exception e) { @@ -98,15 +100,15 @@ private void connect(Handler handler, SocketAddress remoteAddress, Sock return; } if (proxyOptions != null) { - handleProxyConnect(handler, remoteAddress, peerAddress, serverName, ssl, useAlpn, p); + handleProxyConnect(handler, remoteAddress, peerAddress, serverName, ssl, sslOptions, p); } else { - handleConnect(handler, remoteAddress, peerAddress, serverName, ssl, useAlpn, p); + handleConnect(handler, remoteAddress, peerAddress, serverName, ssl, sslOptions, p); } } - private void initSSL(Handler handler, SocketAddress peerAddress, String serverName, boolean ssl, boolean useAlpn, Channel ch, Promise channelHandler) { + private void initSSL(Handler handler, SocketAddress peerAddress, String serverName, boolean ssl, ClientSSLOptions sslOptions, Channel ch, Promise channelHandler) { if (ssl) { - SslHandler sslHandler = sslContextProvider.createClientSslHandler(peerAddress, serverName, useAlpn); + SslHandler sslHandler = sslContextProvider.createClientSslHandler(peerAddress, serverName, sslOptions.isUseAlpn(), sslOptions.isTrustAll(), sslOptions.getSslHandshakeTimeout(), sslOptions.getSslHandshakeTimeoutUnit()); ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("ssl", sslHandler); pipeline.addLast(new ChannelInboundHandlerAdapter() { @@ -140,13 +142,13 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { } - private void handleConnect(Handler handler, SocketAddress remoteAddress, SocketAddress peerAddress, String serverName, boolean ssl, boolean useAlpn, Promise channelHandler) { + private void handleConnect(Handler handler, SocketAddress remoteAddress, SocketAddress peerAddress, String serverName, boolean ssl, ClientSSLOptions sslOptions, Promise channelHandler) { VertxInternal vertx = context.owner(); bootstrap.resolver(vertx.nettyAddressResolverGroup()); bootstrap.handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) { - initSSL(handler, peerAddress, serverName, ssl, useAlpn, ch, channelHandler); + initSSL(handler, peerAddress, serverName, ssl, sslOptions, ch, channelHandler); } }); ChannelFuture fut = bootstrap.connect(vertx.transport().convert(remoteAddress)); @@ -178,7 +180,7 @@ private void connected(Handler handler, Channel channel, boolean ssl, P /** * A channel provider that connects via a Proxy : HTTP or SOCKS */ - private void handleProxyConnect(Handler handler, SocketAddress remoteAddress, SocketAddress peerAddress, String serverName, boolean ssl, boolean useAlpn, Promise channelHandler) { + private void handleProxyConnect(Handler handler, SocketAddress remoteAddress, SocketAddress peerAddress, String serverName, boolean ssl, ClientSSLOptions sslOptions, Promise channelHandler) { final VertxInternal vertx = context.owner(); final String proxyHost = proxyOptions.getHost(); @@ -224,7 +226,7 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { if (evt instanceof ProxyConnectionEvent) { pipeline.remove(proxy); pipeline.remove(this); - initSSL(handler, peerAddress, serverName, ssl, useAlpn, ch, channelHandler); + initSSL(handler, peerAddress, serverName, ssl, sslOptions, ch, channelHandler); connected(handler, ch, ssl, channelHandler); } ctx.fireUserEventTriggered(evt); diff --git a/src/main/java/io/vertx/core/net/impl/CleanableNetClient.java b/src/main/java/io/vertx/core/net/impl/CleanableNetClient.java index 0f3944d18ca..38c835ce9a9 100644 --- a/src/main/java/io/vertx/core/net/impl/CleanableNetClient.java +++ b/src/main/java/io/vertx/core/net/impl/CleanableNetClient.java @@ -17,6 +17,7 @@ import io.vertx.core.spi.metrics.Metrics; import java.lang.ref.Cleaner; +import java.util.Objects; import java.util.concurrent.TimeUnit; /** @@ -75,16 +76,13 @@ public Future connect(SocketAddress remoteAddress, String serverName) } @Override - public Future close() { - action.timeout = 0L; - action.timeUnit = TimeUnit.SECONDS; - cleanable.clean(); - return (client).closeFuture(); + public Future connect(ConnectOptions connectOptions) { + return client.connect(connectOptions); } @Override - public Future updateSSLOptions(SSLOptions options) { - return client.updateSSLOptions(options); + public Future updateSSLOptions(ClientSSLOptions options, boolean force) { + return client.updateSSLOptions(options, force); } @Override @@ -93,8 +91,8 @@ public void close(Promise completion) { } @Override - public void connectInternal(ProxyOptions proxyOptions, SocketAddress remoteAddress, SocketAddress peerAddress, String serverName, boolean ssl, boolean useAlpn, boolean registerWriteHandlers, Promise connectHandler, ContextInternal context, int remainingAttempts) { - client.connectInternal(proxyOptions, remoteAddress, peerAddress, serverName, ssl, useAlpn, registerWriteHandlers, connectHandler, context, remainingAttempts); + public void connectInternal(ConnectOptions connectOptions, Promise connectHandler, ContextInternal context) { + client.connectInternal(connectOptions, connectHandler, context); } @Override @@ -109,7 +107,13 @@ public Future shutdown(long timeout, TimeUnit timeUnit) { @Override public Future close(long timeout, TimeUnit timeUnit) { - return client.close(timeout, timeUnit); + if (timeout < 0L) { + throw new IllegalArgumentException("Invalid timeout: " + timeout); + } + action.timeout = timeout; + action.timeUnit = Objects.requireNonNull(TimeUnit.SECONDS); + cleanable.clean(); + return (client).closeFuture(); } @Override diff --git a/src/main/java/io/vertx/core/net/impl/ConnectionBase.java b/src/main/java/io/vertx/core/net/impl/ConnectionBase.java index 1a5ad95e9dd..24b964c8f2a 100644 --- a/src/main/java/io/vertx/core/net/impl/ConnectionBase.java +++ b/src/main/java/io/vertx/core/net/impl/ConnectionBase.java @@ -41,6 +41,7 @@ import java.security.cert.Certificate; import java.util.Arrays; import java.util.List; +import java.util.function.Predicate; import static io.vertx.core.spi.metrics.Metrics.METRICS_ENABLED; @@ -76,7 +77,6 @@ public abstract class ConnectionBase { protected final ContextInternal context; private Handler exceptionHandler; private Handler closeHandler; - private int writeInProgress; private Object metric; private SocketAddress remoteAddress; private SocketAddress realRemoteAddress; @@ -86,11 +86,14 @@ public abstract class ConnectionBase { private Future closeFuture; private long remainingBytesRead; private long remainingBytesWritten; + private final OutboundMessageQueue messageQueue; // State accessed exclusively from the event loop thread private boolean read; private boolean needsFlush; private boolean closed; + private boolean draining; + private boolean channelWritable; protected ConnectionBase(ContextInternal context, ChannelHandlerContext chctx) { this.vertx = context.owner(); @@ -98,6 +101,8 @@ protected ConnectionBase(ContextInternal context, ChannelHandlerContext chctx) { this.context = context; this.voidPromise = new VoidChannelPromise(chctx.channel(), false); this.closePromise = chctx.newPromise(); + this.channelWritable = chctx.channel().isWritable(); + this.messageQueue = new InternalMessageQueue(chctx.channel().eventLoop()); PromiseInternal p = context.promise(); closePromise.addListener(p); @@ -160,24 +165,29 @@ final void read(Object msg) { } /** - * This method is exclusively called on the event-loop thread + * Like {@link #write(Object, boolean, ChannelPromise)}. + */ + public void write(Object msg, boolean forceFlush, FutureListener promise) { + write(msg, forceFlush, wrap(promise)); + } + + /** + * This method must be exclusively called on the event-loop thread. + * + *

This method directly writes to the channel pipeline and bypasses the outbound queue.

* * @param msg the message to write - * @param flush a {@code null} {@code flush} value means to flush when there is no read in progress, otherwise it will apply the policy + * @param forceFlush flush when {@code true} or there is no read in progress * @param promise the promise receiving the completion event */ - private void write(Object msg, Boolean flush, ChannelPromise promise) { + public void write(Object msg, boolean forceFlush, ChannelPromise promise) { + assert chctx.executor().inEventLoop(); if (METRICS_ENABLED) { reportsBytesWritten(msg); } - boolean writeAndFlush; - if (flush == null) { - writeAndFlush = !read; - } else { - writeAndFlush = flush; - } - needsFlush = !writeAndFlush; - if (writeAndFlush) { + boolean flush = (!read && !draining) || forceFlush; + needsFlush = !flush; + if (flush) { chctx.writeAndFlush(msg, promise); } else { chctx.write(msg, promise); @@ -204,52 +214,40 @@ private void writeClose(PromiseInternal promise) { writeToChannel(Unpooled.EMPTY_BUFFER, true, channelPromise); } - private ChannelPromise wrap(FutureListener handler) { + protected final ChannelPromise wrap(FutureListener handler) { ChannelPromise promise = chctx.newPromise(); promise.addListener(handler); return promise; } - public final void writeToChannel(Object msg, FutureListener listener) { - writeToChannel(msg, listener == null ? voidPromise : wrap(listener)); + public final boolean writeToChannel(Object obj) { + return writeToChannel(obj, voidPromise); } - public final void writeToChannel(Object msg, ChannelPromise promise) { - writeToChannel(msg, false, promise); + public final boolean writeToChannel(Object msg, FutureListener listener) { + return writeToChannel(msg, listener == null ? voidPromise : wrap(listener)); } - public final void writeToChannel(Object msg, boolean forceFlush, ChannelPromise promise) { - synchronized (this) { - if (!chctx.executor().inEventLoop() || writeInProgress > 0) { - // Make sure we serialize all the messages as this method can be called from various threads: - // two "sequential" calls to writeToChannel (we can say that as it is synchronized) should preserve - // the message order independently of the thread. To achieve this we need to reschedule messages - // not on the event loop or if there are pending async message for the channel. - queueForWrite(msg, forceFlush, promise); - return; - } - } - // On the event loop thread - write(msg, forceFlush ? true : null, promise); + public final boolean writeToChannel(Object msg, ChannelPromise promise) { + return writeToChannel(msg, false, promise); } - private void queueForWrite(Object msg, boolean forceFlush, ChannelPromise promise) { - writeInProgress++; - chctx.executor().execute(() -> { - boolean flush; - if (forceFlush) { - flush = true; - } else { - synchronized (this) { - flush = --writeInProgress == 0; - } + public final boolean writeToChannel(Object msg, boolean forceFlush, ChannelPromise promise) { + return writeToChannel(new MessageWrite() { + @Override + public void write() { + ConnectionBase.this.write(msg, forceFlush, promise); + } + + @Override + public void cancel(Throwable cause) { + promise.setFailure(cause); } - write(msg, flush, promise); }); } - public void writeToChannel(Object obj) { - writeToChannel(obj, voidPromise); + public final boolean writeToChannel(MessageWrite msg) { + return messageQueue.write(msg); } /** @@ -268,9 +266,20 @@ public final void flush(ChannelPromise promise) { writeToChannel(Unpooled.EMPTY_BUFFER, true, promise); } - // This is a volatile read inside the Netty channel implementation - public boolean isNotWritable() { - return !chctx.channel().isWritable(); + /** + * Asynchronous flush. + * + * @param listener the listener notified when flush occurred + */ + public final void flush(FutureListener listener) { + writeToChannel(Unpooled.EMPTY_BUFFER, true, listener == null ? voidPromise : wrap(listener)); + } + + /** + * @return the write queue writability status + */ + public boolean writeQueueFull() { + return !messageQueue.isWritable(); } /** @@ -363,6 +372,10 @@ protected void handleException(Throwable t) { protected void handleClosed() { closed = true; + List pending = messageQueue.clear(); + for (MessageWrite msg : pending) { + msg.cancel(CLOSED_EXCEPTION); + } NetworkMetrics metrics = metrics(); if (metrics != null) { flushBytesRead(); @@ -401,7 +414,21 @@ protected void handleIdle(IdleStateEvent event) { chctx.close(); } - protected abstract void handleInterestedOpsChanged(); + /** + * Called when the connection write queue is drained + */ + protected void handleWriteQueueDrained() { + } + + protected void handleMessage(Object msg) { + } + + void channelWritabilityChanged() { + channelWritable = chctx.channel().isWritable(); + if (channelWritable) { + messageQueue.drain(); + } + } protected boolean supportsFileRegion() { return vertx.transport().supportFileRegion() && !isSsl() &&!isTrafficShaped(); @@ -628,6 +655,9 @@ public SocketAddress remoteAddress() { address = socketAdressOverride(REMOTE_ADDRESS_OVERRIDE); if (address == null) { address = channelRemoteAddress(); + if (address != null && address.isDomainSocket() && address.path().isEmpty()) { + address = channelLocalAddress(); + } } if (address != null) { remoteAddress = address; @@ -653,10 +683,6 @@ public SocketAddress remoteAddress(boolean real) { private SocketAddress channelLocalAddress() { java.net.SocketAddress addr = chctx.channel().localAddress(); - if (addr == null && channel().getClass().getSimpleName().endsWith("DomainSocketChannel")) { - // Workaround bug https://github.com/netty/netty/issues/13417 - return SocketAddress.domainSocketAddress(""); - } return addr != null ? vertx.transport().convert(addr) : null; } @@ -666,6 +692,9 @@ public SocketAddress localAddress() { address = socketAdressOverride(LOCAL_ADDRESS_OVERRIDE); if (address == null) { address = channelLocalAddress(); + if (address != null && address.isDomainSocket() && address.path().isEmpty()) { + address = channelRemoteAddress(); + } } if (address != null) { localAddress = address; @@ -689,6 +718,42 @@ public SocketAddress localAddress(boolean real) { } } - protected void handleMessage(Object msg) { + /** + * Version of {@link OutboundMessageQueue} accessing internal connection base state. + */ + private class InternalMessageQueue extends OutboundMessageQueue implements Predicate { + + public InternalMessageQueue(EventLoop eventLoop) { + super(eventLoop); + } + + @Override + public boolean test(MessageWrite msg) { + if (channelWritable) { + msg.write(); + return true; + } else { + return false; + } + } + + @Override + protected void startDraining() { + draining = true; + } + + @Override + protected void stopDraining() { + draining = false; + if (!read && needsFlush) { + needsFlush = false; + chctx.flush(); + } + } + + @Override + protected void writeQueueDrained() { + ConnectionBase.this.handleWriteQueueDrained(); + } } } diff --git a/src/main/java/io/vertx/core/net/impl/HostAndPortImpl.java b/src/main/java/io/vertx/core/net/impl/HostAndPortImpl.java new file mode 100644 index 00000000000..c6cd671231f --- /dev/null +++ b/src/main/java/io/vertx/core/net/impl/HostAndPortImpl.java @@ -0,0 +1,183 @@ +package io.vertx.core.net.impl; + +import io.vertx.core.net.HostAndPort; + +public class HostAndPortImpl implements HostAndPort { + + public static int parseHost(String val, int from, int to) { + int pos; + if ((pos = parseIPLiteral(val, from, to)) != -1) { + return pos; + } else if ((pos = parseIPv4Address(val, from, to)) != -1) { + return pos; + } else if ((pos = parseRegName(val, from, to)) != -1) { + return pos; + } + return -1; + } + + private static int foo(int v) { + return v == -1 ? -1 : v + 1; + } + + public static int parseIPv4Address(String s, int from, int to) { + for (int i = 0;i < 4;i++) { + if (i > 0 && from < to && s.charAt(from++) != '.') { + return -1; + } + from = parseDecOctet(s, from, to); + if (from == -1) { + return -1; + } + } + return from < to && (from + 1 == s.length() || s.charAt(from + 1) != ':') ? -1 : from; + } + + static int parseDecOctet(String s, int from, int to) { + int val = parseDigit(s, from++, to); + switch (val) { + case 0: + return from; + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + int n = parseDigit(s, from, to); + if (n != -1) { + val = val * 10 + n; + n = parseDigit(s, ++from, to); + if (n != -1) { + from++; + val = val * 10 + n; + } + } + if (val < 256) { + return from; + } + } + return -1; + } + + private static int parseDigit(String s, int from, int to) { + char c; + return from < to && isDIGIT(c = s.charAt(from)) ? c - '0' : -1; + } + + public static int parseIPLiteral(String s, int from, int to) { + return from + 2 < to && s.charAt(from) == '[' ? foo(s.indexOf(']', from + 2)) : -1; + } + + public static int parseRegName(String s, int from, int to) { + while (from < to) { + char c = s.charAt(from); + if (isUnreserved(c) || isSubDelims(c)) { + from++; + } else if (c == '%' && (from + 2) < to && isHEXDIG(s.charAt(c + 1)) && isHEXDIG(s.charAt(c + 2))) { + from += 3; + } else { + break; + } + } + return from; + } + + private static boolean isUnreserved(char ch) { + return isALPHA(ch) || isDIGIT(ch) || ch == '-' || ch == '.' || ch == '_' || ch == '~'; + } + + private static boolean isALPHA(char ch) { + return ('A' <= ch && ch <= 'Z') + || ('a'<= ch && ch <= 'z'); + } + + private static boolean isDIGIT(char ch) { + return ('0' <= ch && ch <= '9'); + } + + private static boolean isSubDelims(char ch) { + return ch == '!' || ch == '$' || ch == '&' || ch == '\'' || ch == '(' || ch == ')' || ch == '*' || ch == '+' || ch == ',' || ch == ';' || ch == '='; + } + + static boolean isHEXDIG(char ch) { + return isDIGIT(ch) || ('A' <= ch && ch <= 'F') || ('a' <= ch && ch <= 'f'); + } + + /** + * Parse an authority HTTP header, that is host [':' port] + * @param s the string to parse + * @param schemePort the scheme port used when the optional port is not specified + * @return the parsed value or {@code null} when the string cannot be parsed + */ + public static HostAndPortImpl parseAuthority(String s, int schemePort) { + int pos = parseHost(s, 0, s.length()); + if (pos == s.length()) { + return new HostAndPortImpl(s, schemePort); + } + if (pos < s.length() && s.charAt(pos) == ':') { + String host = s.substring(0, pos); + int port = 0; + while (++pos < s.length()) { + int digit = parseDigit(s, pos, s.length()); + if (digit == -1) { + return null; + } + port = port * 10 + digit; + if (port > 65535) { + return null; + } + } + return new HostAndPortImpl(host, port); + } + return null; + } + + private final String host; + private final int port; + + public HostAndPortImpl(String host, int port) { + if (host == null) { + throw new NullPointerException(); + } + this.host = host; + this.port = port; + } + + public String host() { + return host; + } + + public int port() { + return port; + } + + @Override + public int hashCode() { + return host.hashCode() ^ port; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof HostAndPort) { + HostAndPort that = (HostAndPort) obj; + return port == that.port() && host.equals(that.host()); + } + return false; + } + + @Override + public String toString() { + if (port >= 0) { + return host + ':' + port; + } else { + return host; + } + } +} diff --git a/src/main/java/io/vertx/core/net/impl/KeyStoreHelper.java b/src/main/java/io/vertx/core/net/impl/KeyStoreHelper.java index 90709d53957..d715053a3f9 100644 --- a/src/main/java/io/vertx/core/net/impl/KeyStoreHelper.java +++ b/src/main/java/io/vertx/core/net/impl/KeyStoreHelper.java @@ -65,7 +65,7 @@ public class KeyStoreHelper { // Dummy password for encrypting pem based stores in memory - public static final String DUMMY_PASSWORD = "dummy"; + public static final String DUMMY_PASSWORD = "dummdummydummydummydummydummydummy"; // at least 32 characters for compat with FIPS mode private static final String DUMMY_CERT_ALIAS = "cert-"; private static final Pattern BEGIN_PATTERN = Pattern.compile("-----BEGIN ([A-Z ]+)-----"); @@ -159,10 +159,10 @@ public static KeyManagerFactory toKeyManagerFactory(X509KeyManager mgr) throws E String keyStoreType = KeyStore.getDefaultType(); KeyStore ks = KeyStore.getInstance(keyStoreType); ks.load(null, null); - ks.setKeyEntry("key", mgr.getPrivateKey(null), new char[0], mgr.getCertificateChain(null)); + ks.setKeyEntry("key", mgr.getPrivateKey(null), DUMMY_PASSWORD.toCharArray(), mgr.getCertificateChain(null)); String keyAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); KeyManagerFactory kmf = KeyManagerFactory.getInstance(keyAlgorithm); - kmf.init(ks, new char[0]); + kmf.init(ks, DUMMY_PASSWORD.toCharArray()); return kmf; } diff --git a/src/main/java/io/vertx/core/cli/converters/Converter.java b/src/main/java/io/vertx/core/net/impl/MessageWrite.java similarity index 52% rename from src/main/java/io/vertx/core/cli/converters/Converter.java rename to src/main/java/io/vertx/core/net/impl/MessageWrite.java index d1f920eec51..afa4953f3f3 100644 --- a/src/main/java/io/vertx/core/cli/converters/Converter.java +++ b/src/main/java/io/vertx/core/net/impl/MessageWrite.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -8,17 +8,25 @@ * * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ - -package io.vertx.core.cli.converters; +package io.vertx.core.net.impl; /** - * The converter interface to convert {@code String}s to {@code Object}s. - * - * @author Clement Escoffier + * A message write operation. */ @FunctionalInterface -public interface Converter { +public interface MessageWrite { + + /** + * Write the message. + */ + void write(); - T fromString(String s); + /** + * Cancel the write operation. + * + * @param cause the cancellation cause + */ + default void cancel(Throwable cause) { + } } diff --git a/src/main/java/io/vertx/core/net/impl/NetClientImpl.java b/src/main/java/io/vertx/core/net/impl/NetClientImpl.java index 6a7d79a9819..e82463b0f28 100644 --- a/src/main/java/io/vertx/core/net/impl/NetClientImpl.java +++ b/src/main/java/io/vertx/core/net/impl/NetClientImpl.java @@ -23,6 +23,7 @@ import io.vertx.core.Future; import io.vertx.core.Promise; import io.vertx.core.buffer.impl.PartialPooledByteBufAllocator; +import io.vertx.core.http.ClientAuth; import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.CloseSequence; import io.vertx.core.impl.VertxInternal; @@ -37,7 +38,6 @@ import java.net.ConnectException; import java.util.Objects; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; /** @@ -58,7 +58,7 @@ class NetClientImpl implements NetClientInternal { private final VertxInternal vertx; private final NetClientOptions options; private final SSLHelper sslHelper; - private Future sslChannelProvider; + private volatile ClientSSLOptions sslOptions; public final ChannelGroup channelGroup; private final TCPMetrics metrics; public ShutdownEvent closeEvent; @@ -78,7 +78,7 @@ public NetClientImpl(VertxInternal vertx, TCPMetrics metrics, NetClientOptions o this.vertx = vertx; this.channelGroup = new DefaultChannelGroup(vertx.getAcceptorEventLoopGroup().next()); this.options = new NetClientOptions(options); - this.sslHelper = new SSLHelper(options, options.getApplicationLayerProtocols()); + this.sslHelper = new SSLHelper(SSLHelper.resolveEngineOptions(options.getSslEngineOptions(), options.isUseAlpn())); this.metrics = metrics; this.logEnabled = options.getLogActivity(); this.idleTimeout = options.getIdleTimeout(); @@ -87,13 +87,14 @@ public NetClientImpl(VertxInternal vertx, TCPMetrics metrics, NetClientOptions o this.idleTimeoutUnit = options.getIdleTimeoutUnit(); this.closeSequence = closeSequence1; this.proxyFilter = options.getNonProxyHosts() != null ? ProxyFilter.nonProxyHosts(options.getNonProxyHosts()) : ProxyFilter.DEFAULT_PROXY_FILTER; + this.sslOptions = options.getSslOptions(); } - protected void initChannel(ChannelPipeline pipeline) { + protected void initChannel(ChannelPipeline pipeline, boolean ssl) { if (logEnabled) { pipeline.addLast("logging", new LoggingHandler(options.getActivityLogDataFormat())); } - if (options.isSsl() || !vertx.transport().supportFileRegion()) { + if (ssl || !vertx.transport().supportFileRegion()) { // only add ChunkedWriteHandler when SSL is enabled otherwise it is not needed as FileRegion is used. pipeline.addLast("chunkedWriter", new ChunkedWriteHandler()); // For large file / sendfile support } @@ -114,20 +115,47 @@ public Future connect(int port, String host, String serverName) { @Override public Future connect(SocketAddress remoteAddress) { - return connect(remoteAddress, (String) null); + return connect(remoteAddress, null); } @Override public Future connect(SocketAddress remoteAddress, String serverName) { - return connect(vertx.getOrCreateContext(), remoteAddress, serverName); + ConnectOptions connectOptions = new ConnectOptions(); + connectOptions.setRemoteAddress(remoteAddress); + String peerHost = remoteAddress.host(); + if (peerHost != null && peerHost.endsWith(".")) { + peerHost= peerHost.substring(0, peerHost.length() - 1); + } + if (peerHost != null) { + connectOptions.setHost(peerHost); + connectOptions.setPort(remoteAddress.port()); + } + connectOptions.setSsl(options.isSsl()); + connectOptions.setSniServerName(serverName); + connectOptions.setSslOptions(sslOptions); + return connect(connectOptions); } - public Future connect(ContextInternal context, SocketAddress remoteAddress, String serverName) { + @Override + public Future connect(ConnectOptions connectOptions) { + ContextInternal context = vertx.getOrCreateContext(); Promise promise = context.promise(); - connect(remoteAddress, serverName, promise, context); + connectInternal(connectOptions, options.isRegisterWriteHandler(), promise, context, options.getReconnectAttempts()); return promise.future(); } + @Override + public void connectInternal(ConnectOptions connectOptions, Promise connectHandler, ContextInternal context) { + ClientSSLOptions sslOptions = connectOptions.getSslOptions(); + if (sslOptions == null) { + connectOptions.setSslOptions(this.sslOptions); + if (connectOptions.getSslOptions() == null) { + connectOptions.setSslOptions(new ClientSSLOptions()); // DO WE NEED THIS ??? AVOID NPE + } + } + connectInternal(connectOptions, false, connectHandler, context, 0); + } + private void doShutdown(Promise p) { if (closeEvent == null) { closeEvent = new ShutdownEvent(0, TimeUnit.SECONDS); @@ -201,98 +229,53 @@ public Metrics getMetrics() { } @Override - public Future updateSSLOptions(SSLOptions options) { - Future fut; + public Future updateSSLOptions(ClientSSLOptions options, boolean force) { ContextInternal ctx = vertx.getOrCreateContext(); synchronized (this) { - fut = sslHelper.updateSslContext(new SSLOptions(options), ctx); - sslChannelProvider = fut; + this.sslOptions = options; } - return fut.transform(ar -> { - if (ar.failed()) { - return ctx.failedFuture(ar.cause()); - } else if (ar.succeeded() && ar.result().error() != null) { - return ctx.failedFuture(ar.result().error()); - } else { - return ctx.succeededFuture(); - } - }); + return ctx.succeededFuture(true); } - private void connect(SocketAddress remoteAddress, String serverName, Promise connectHandler, ContextInternal ctx) { + private void connectInternal(ConnectOptions connectOptions, + boolean registerWriteHandlers, + Promise connectHandler, + ContextInternal context, + int remainingAttempts) { if (closeSequence.started()) { - throw new IllegalStateException("Client is closed"); - } - SocketAddress peerAddress = remoteAddress; - String peerHost = peerAddress.host(); - if (peerHost != null && peerHost.endsWith(".")) { - peerAddress = SocketAddress.inetSocketAddress(peerAddress.port(), peerHost.substring(0, peerHost.length() - 1)); - } - ProxyOptions proxyOptions = options.getProxyOptions(); - if (proxyFilter != null) { - if (!proxyFilter.test(remoteAddress)) { - proxyOptions = null; + connectHandler.fail(new IllegalStateException("Client is closed")); + } else { + if (connectOptions.isSsl()) { + // We might be using an SslContext created from a plugged engine + ClientSSLOptions sslOptions = connectOptions.getSslOptions() != null ? connectOptions.getSslOptions() : new ClientSSLOptions(); + Future fut; + fut = sslHelper.resolveSslChannelProvider( + sslOptions, + sslOptions.getHostnameVerificationAlgorithm(), + false, + ClientAuth.NONE, + sslOptions.getApplicationLayerProtocols(), + context); + fut.onComplete(ar -> { + if (ar.succeeded()) { + connectInternal2(connectOptions, sslOptions, ar.result(), registerWriteHandlers, connectHandler, context, remainingAttempts); + } else { + connectHandler.fail(ar.cause()); + } + }); + } else { + connectInternal2(connectOptions, connectOptions.getSslOptions(), null, registerWriteHandlers, connectHandler, context, remainingAttempts); } } - connectInternal(proxyOptions, remoteAddress, peerAddress, serverName, options.isSsl(), options.isUseAlpn(), options.isRegisterWriteHandler(), connectHandler, ctx, options.getReconnectAttempts()); } - /** - * Open a socket to the {@code remoteAddress} server. - * - * @param proxyOptions optional proxy configuration - * @param remoteAddress the server address - * @param peerAddress the peer address (along with SSL) - * @param serverName the SNI server name (along with SSL) - * @param ssl whether to use SSL - * @param useAlpn wether to use ALPN (along with SSL) - * @param registerWriteHandlers whether to register event-bus write handlers - * @param connectHandler the promise to resolve with the connect result - * @param context the socket context - * @param remainingAttempts how many times reconnection is reattempted - */ - public void connectInternal(ProxyOptions proxyOptions, - SocketAddress remoteAddress, - SocketAddress peerAddress, - String serverName, - boolean ssl, - boolean useAlpn, + private void connectInternal2(ConnectOptions connectOptions, + ClientSSLOptions sslOptions, + SslChannelProvider sslChannelProvider, boolean registerWriteHandlers, Promise connectHandler, ContextInternal context, int remainingAttempts) { - if (closeSequence.started()) { - connectHandler.fail(new IllegalStateException("Client is closed")); - } else { - Future fut; - synchronized (NetClientImpl.this) { - fut = sslChannelProvider; - if (fut == null) { - fut = sslHelper.updateSslContext(options.getSslOptions(), context); - sslChannelProvider = fut; - } - } - fut.onComplete(ar -> { - if (ar.succeeded()) { - connectInternal2(proxyOptions, remoteAddress, peerAddress, ar.result().sslChannelProvider(), serverName, ssl, useAlpn, registerWriteHandlers, connectHandler, context, remainingAttempts); - } else { - connectHandler.fail(ar.cause()); - } - }); - } - } - - private void connectInternal2(ProxyOptions proxyOptions, - SocketAddress remoteAddress, - SocketAddress peerAddress, - SslChannelProvider sslChannelProvider, - String serverName, - boolean ssl, - boolean useAlpn, - boolean registerWriteHandlers, - Promise connectHandler, - ContextInternal context, - int remainingAttempts) { EventLoop eventLoop = context.nettyEventLoop(); if (eventLoop.inEventLoop()) { @@ -301,14 +284,54 @@ private void connectInternal2(ProxyOptions proxyOptions, bootstrap.group(eventLoop); bootstrap.option(ChannelOption.ALLOCATOR, PartialPooledByteBufAllocator.INSTANCE); - vertx.transport().configure(options, remoteAddress.isDomainSocket(), bootstrap); + SocketAddress remoteAddress = connectOptions.getRemoteAddress(); + if (remoteAddress == null) { + String host = connectOptions.getHost(); + Integer port = connectOptions.getPort(); + if (host == null || port == null) { + throw new UnsupportedOperationException("handle me"); + } + remoteAddress = SocketAddress.inetSocketAddress(port, host); + } + + SocketAddress peerAddress = peerAddress(remoteAddress, connectOptions); + + int connectTimeout = connectOptions.getTimeout(); + if (connectTimeout < 0) { + connectTimeout = options.getConnectTimeout(); + } + vertx.transport().configure(options, connectTimeout, remoteAddress.isDomainSocket(), bootstrap); + + ProxyOptions proxyOptions = connectOptions.getProxyOptions(); + if (proxyOptions == null) { + proxyOptions = options.getProxyOptions(); + } + if (proxyFilter != null) { + if (!proxyFilter.test(remoteAddress)) { + proxyOptions = null; + } + } ChannelProvider channelProvider = new ChannelProvider(bootstrap, sslChannelProvider, context) .proxyOptions(proxyOptions); - channelProvider.handler(ch -> connected(context, ch, connectHandler, remoteAddress, sslChannelProvider, channelProvider.applicationProtocol(), registerWriteHandlers)); - - io.netty.util.concurrent.Future fut = channelProvider.connect(remoteAddress, peerAddress, serverName, ssl, useAlpn); + SocketAddress captured = remoteAddress; + + channelProvider.handler(ch -> connected( + context, + sslOptions, + ch, + connectHandler, + captured, + connectOptions.isSsl(), + channelProvider.applicationProtocol(), + registerWriteHandlers)); + io.netty.util.concurrent.Future fut = channelProvider.connect( + remoteAddress, + peerAddress, + connectOptions.getSniServerName(), + connectOptions.isSsl(), + sslOptions); fut.addListener((GenericFutureListener>) future -> { if (!future.isSuccess()) { Throwable cause = future.cause(); @@ -319,7 +342,12 @@ private void connectInternal2(ProxyOptions proxyOptions, log.debug("Failed to create connection. Will retry in " + options.getReconnectInterval() + " milliseconds"); //Set a timer to retry connection vertx.setTimer(options.getReconnectInterval(), tid -> - connectInternal(proxyOptions, remoteAddress, peerAddress, serverName, ssl, useAlpn, registerWriteHandlers, connectHandler, context, remainingAttempts == -1 ? remainingAttempts : remainingAttempts - 1) + connectInternal( + connectOptions, + registerWriteHandlers, + connectHandler, + context, + remainingAttempts == -1 ? remainingAttempts : remainingAttempts - 1) ); }); } else { @@ -328,14 +356,50 @@ private void connectInternal2(ProxyOptions proxyOptions, } }); } else { - eventLoop.execute(() -> connectInternal2(proxyOptions, remoteAddress, peerAddress, sslChannelProvider, serverName, ssl, useAlpn, registerWriteHandlers, connectHandler, context, remainingAttempts)); + eventLoop.execute(() -> connectInternal2(connectOptions, sslOptions, sslChannelProvider, registerWriteHandlers, connectHandler, context, remainingAttempts)); + } + } + + private static SocketAddress peerAddress(SocketAddress remoteAddress, ConnectOptions connectOptions) { + if (!connectOptions.isSsl()) { + return null; + } + String peerHost = connectOptions.getHost(); + Integer peerPort = connectOptions.getPort(); + if (remoteAddress.isInetSocket()) { + if ((peerHost == null || peerHost.equals(remoteAddress.host())) + && (peerPort == null || peerPort.intValue() == remoteAddress.port())) { + return remoteAddress; + } + if (peerHost == null) { + peerHost = remoteAddress.host();; + } + if (peerPort == null) { + peerPort = remoteAddress.port(); + } } + return peerHost != null && peerPort != null ? SocketAddress.inetSocketAddress(peerPort, peerHost) : null; } - private void connected(ContextInternal context, Channel ch, Promise connectHandler, SocketAddress remoteAddress, SslChannelProvider sslChannelProvider, String applicationLayerProtocol, boolean registerWriteHandlers) { + private void connected(ContextInternal context, + ClientSSLOptions sslOptions, + Channel ch, + Promise connectHandler, + SocketAddress remoteAddress, + boolean ssl, + String applicationLayerProtocol, + boolean registerWriteHandlers) { channelGroup.add(ch); - initChannel(ch.pipeline()); - VertxHandler handler = VertxHandler.create(ctx -> new NetSocketImpl(context, ctx, remoteAddress, sslChannelProvider, metrics, applicationLayerProtocol, registerWriteHandlers)); + initChannel(ch.pipeline(), ssl); + VertxHandler handler = VertxHandler.create(ctx -> new NetSocketImpl( + context, + ctx, + remoteAddress, + sslHelper, + sslOptions, + metrics, + applicationLayerProtocol, + registerWriteHandlers)); handler.removeHandler(NetSocketImpl::unregisterEventBusHandler); handler.addHandler(sock -> { if (metrics != null) { diff --git a/src/main/java/io/vertx/core/net/impl/NetClientInternal.java b/src/main/java/io/vertx/core/net/impl/NetClientInternal.java index e525fb14df0..3f66eba27cc 100644 --- a/src/main/java/io/vertx/core/net/impl/NetClientInternal.java +++ b/src/main/java/io/vertx/core/net/impl/NetClientInternal.java @@ -24,38 +24,20 @@ public interface NetClientInternal extends NetClient, MetricsProvider, Closeable /** * Open a socket to the {@code remoteAddress} server. * - * @param proxyOptions optional proxy configuration - * @param remoteAddress the server address - * @param peerAddress the peer address (along with SSL) - * @param serverName the SNI server name (along with SSL) - * @param ssl whether to use SSL - * @param useAlpn wether to use ALPN (along with SSL) - * @param registerWriteHandlers whether to register event-bus write handlers + * @param connectOptions the connect options * @param connectHandler the promise to resolve with the connect result - * @param context the socket context - * @param remainingAttempts how many times reconnection is reattempted + * @param context the socket context */ - void connectInternal(ProxyOptions proxyOptions, - SocketAddress remoteAddress, - SocketAddress peerAddress, - String serverName, - boolean ssl, - boolean useAlpn, - boolean registerWriteHandlers, - Promise connectHandler, - ContextInternal context, - int remainingAttempts); - - @Override - default Future close() { - return close(0L, TimeUnit.SECONDS); - } + void connectInternal(ConnectOptions connectOptions, + Promise connectHandler, + ContextInternal context); Future closeFuture(); /** * Shutdown the client, a {@link ShutdownEvent} is broadcast to all channels. The operation completes - * when all channels are closed or the timeout expires. + * when all channels are closed or the timeout expires. This method does not close the remaining connections + * forcibly. * * @param timeout the shutdown timeout * @param timeUnit the shutdown timeout unit @@ -63,6 +45,4 @@ default Future close() { */ Future shutdown(long timeout, TimeUnit timeUnit); - Future close(long timeout, TimeUnit timeUnit); - } diff --git a/src/main/java/io/vertx/core/net/impl/NetServerImpl.java b/src/main/java/io/vertx/core/net/impl/NetServerImpl.java index 80323425192..1ea0e204f97 100644 --- a/src/main/java/io/vertx/core/net/impl/NetServerImpl.java +++ b/src/main/java/io/vertx/core/net/impl/NetServerImpl.java @@ -14,7 +14,6 @@ import io.netty.channel.Channel; import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPromise; -import io.netty.channel.EventLoopGroup; import io.netty.handler.codec.haproxy.HAProxyMessageDecoder; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.stream.ChunkedWriteHandler; @@ -27,17 +26,10 @@ import io.vertx.core.Promise; import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.VertxInternal; -import io.vertx.core.net.NetServer; -import io.vertx.core.net.NetServerOptions; -import io.vertx.core.net.NetSocket; -import io.vertx.core.net.SocketAddress; -import io.vertx.core.net.TrafficShapingOptions; +import io.vertx.core.net.*; import io.vertx.core.spi.metrics.MetricsProvider; import io.vertx.core.spi.metrics.TCPMetrics; import io.vertx.core.spi.metrics.VertxMetrics; -import io.vertx.core.streams.ReadStream; - -import java.util.function.BiConsumer; /** * @@ -122,7 +114,7 @@ public Future close() { } @Override - protected BiConsumer childHandler(ContextInternal context, SocketAddress socketAddress, GlobalTrafficShapingHandler trafficShapingHandler) { + protected Worker childHandler(ContextInternal context, SocketAddress socketAddress, GlobalTrafficShapingHandler trafficShapingHandler) { return new NetServerWorker(context, handler, exceptionHandler, trafficShapingHandler); } @@ -156,7 +148,7 @@ public boolean isClosed() { return !isListening(); } - private class NetServerWorker implements BiConsumer { + private class NetServerWorker implements Worker { private final ContextInternal context; private final Handler connectionHandler; @@ -171,7 +163,7 @@ private class NetServerWorker implements BiConsumer } @Override - public void accept(Channel ch, SslChannelProvider sslChannelProvider) { + public void accept(Channel ch, SslChannelProvider sslChannelProvider, SSLHelper sslHelper, ServerSSLOptions sslOptions) { if (!NetServerImpl.this.accept()) { ch.close(); return; @@ -191,31 +183,31 @@ public void accept(Channel ch, SslChannelProvider sslChannelProvider) { if (idle != null) { ch.pipeline().remove(idle); } - configurePipeline(future.getNow(), sslChannelProvider); + configurePipeline(future.getNow(), sslChannelProvider, sslHelper, sslOptions); } else { //No need to close the channel.HAProxyMessageDecoder already did handleException(future.cause()); } }); } else { - configurePipeline(ch, sslChannelProvider); + configurePipeline(ch, sslChannelProvider, sslHelper, sslOptions); } } - private void configurePipeline(Channel ch, SslChannelProvider sslChannelProvider) { + private void configurePipeline(Channel ch, SslChannelProvider sslChannelProvider, SSLHelper sslHelper, SSLOptions sslOptions) { if (options.isSsl()) { - ch.pipeline().addLast("ssl", sslChannelProvider.createServerHandler()); + ch.pipeline().addLast("ssl", sslChannelProvider.createServerHandler(options.isUseAlpn(), options.getSslHandshakeTimeout(), options.getSslHandshakeTimeoutUnit())); ChannelPromise p = ch.newPromise(); ch.pipeline().addLast("handshaker", new SslHandshakeCompletionHandler(p)); p.addListener(future -> { if (future.isSuccess()) { - connected(ch, sslChannelProvider); + connected(ch, sslHelper, sslOptions); } else { handleException(future.cause()); } }); } else { - connected(ch, sslChannelProvider); + connected(ch, sslHelper, sslOptions); } if (trafficShapingHandler != null) { ch.pipeline().addFirst("globalTrafficShaping", trafficShapingHandler); @@ -228,10 +220,10 @@ private void handleException(Throwable cause) { } } - private void connected(Channel ch, SslChannelProvider sslChannelProvider) { + private void connected(Channel ch, SSLHelper sslHelper, SSLOptions sslOptions) { NetServerImpl.this.initChannel(ch.pipeline(), options.isSsl()); TCPMetrics metrics = getMetrics(); - VertxHandler handler = VertxHandler.create(ctx -> new NetSocketImpl(context, ctx, sslChannelProvider, metrics, options.isRegisterWriteHandler())); + VertxHandler handler = VertxHandler.create(ctx -> new NetSocketImpl(context, ctx, sslHelper, sslOptions, metrics, options.isRegisterWriteHandler())); handler.removeHandler(NetSocketImpl::unregisterEventBusHandler); handler.addHandler(conn -> { if (metrics != null) { @@ -259,52 +251,4 @@ protected void initChannel(ChannelPipeline pipeline, boolean ssl) { pipeline.addLast("idle", new IdleStateHandler(readIdleTimeout, writeIdleTimeout, idleTimeout, options.getIdleTimeoutUnit())); } } - - /* - Needs to be protected using the NetServerImpl monitor as that protects the listening variable - In practice synchronized overhead should be close to zero assuming most access is from the same thread due - to biased locks - */ - private class NetSocketStream implements ReadStream { - - - - @Override - public NetSocketStream handler(Handler handler) { - connectHandler(handler); - return this; - } - - @Override - public NetSocketStream pause() { - pauseAccepting(); - return this; - } - - @Override - public NetSocketStream resume() { - resumeAccepting(); - return this; - } - - @Override - public ReadStream fetch(long amount) { - fetchAccepting(amount); - return this; - } - - @Override - public NetSocketStream endHandler(Handler handler) { - synchronized (NetServerImpl.this) { - endHandler = handler; - return this; - } - } - - @Override - public NetSocketStream exceptionHandler(Handler handler) { - // Should we use it in the server close exception handler ? - return this; - } - } } diff --git a/src/main/java/io/vertx/core/net/impl/NetSocketImpl.java b/src/main/java/io/vertx/core/net/impl/NetSocketImpl.java index 90802a526df..cc08eb732d6 100644 --- a/src/main/java/io/vertx/core/net/impl/NetSocketImpl.java +++ b/src/main/java/io/vertx/core/net/impl/NetSocketImpl.java @@ -19,19 +19,16 @@ import io.netty.channel.ChannelPromise; import io.netty.util.CharsetUtil; import io.netty.util.ReferenceCounted; -import io.vertx.core.AsyncResult; +import io.vertx.codegen.annotations.Nullable; import io.vertx.core.Future; import io.vertx.core.Handler; -import io.vertx.core.Promise; import io.vertx.core.buffer.Buffer; +import io.vertx.core.buffer.impl.BufferInternal; import io.vertx.core.eventbus.Message; import io.vertx.core.eventbus.MessageConsumer; import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.future.PromiseInternal; -import io.vertx.core.impl.logging.Logger; -import io.vertx.core.impl.logging.LoggerFactory; -import io.vertx.core.net.NetSocket; -import io.vertx.core.net.SocketAddress; +import io.vertx.core.net.*; import io.vertx.core.spi.metrics.TCPMetrics; import io.vertx.core.streams.impl.InboundBuffer; @@ -52,34 +49,41 @@ */ public class NetSocketImpl extends ConnectionBase implements NetSocketInternal { - private static final Logger log = LoggerFactory.getLogger(NetSocketImpl.class); - private final String writeHandlerID; - private final SslChannelProvider sslChannelProvider; + private final SSLHelper sslHelper; + private final SSLOptions sslOptions; private final SocketAddress remoteAddress; private final TCPMetrics metrics; private final InboundBuffer pending; private final String negotiatedApplicationLayerProtocol; private Handler endHandler; - private Handler drainHandler; + private volatile Handler drainHandler; private MessageConsumer registration; private Handler handler; private Handler messageHandler; private Handler eventHandler; + private Handler shutdownHandler; - public NetSocketImpl(ContextInternal context, ChannelHandlerContext channel, SslChannelProvider sslChannelProvider, TCPMetrics metrics, boolean registerWriteHandler) { - this(context, channel, null, sslChannelProvider, metrics, null, registerWriteHandler); + public NetSocketImpl(ContextInternal context, + ChannelHandlerContext channel, + SSLHelper sslHelper, + SSLOptions sslOptions, + TCPMetrics metrics, + boolean registerWriteHandler) { + this(context, channel, null, sslHelper, sslOptions, metrics, null, registerWriteHandler); } public NetSocketImpl(ContextInternal context, ChannelHandlerContext channel, SocketAddress remoteAddress, - SslChannelProvider sslChannelProvider, + SSLHelper sslHelper, + SSLOptions sslOptions, TCPMetrics metrics, String negotiatedApplicationLayerProtocol, boolean registerWriteHandler) { super(context, channel); - this.sslChannelProvider = sslChannelProvider; + this.sslHelper = sslHelper; + this.sslOptions = sslOptions; this.writeHandlerID = registerWriteHandler ? "__vertx.net." + UUID.randomUUID() : null; this.remoteAddress = remoteAddress; this.metrics = metrics; @@ -129,7 +133,7 @@ public String writeHandlerID() { } @Override - public synchronized Future writeMessage(Object message) { + public Future writeMessage(Object message) { PromiseInternal promise = context.promise(); writeToChannel(message, promise); return promise.future(); @@ -142,7 +146,7 @@ public String applicationLayerProtocol() { @Override public Future write(Buffer data) { - return writeMessage(data.getByteBuf()); + return writeMessage(((BufferInternal)data).getByteBuf()); } @Override @@ -206,7 +210,15 @@ public NetSocket setWriteQueueMaxSize(int maxSize) { @Override public boolean writeQueueFull() { - return isNotWritable(); + return super.writeQueueFull(); + } + + @Override + protected void handleWriteQueueDrained() { + Handler handler = drainHandler; + if (handler != null) { + context.emit(null, handler); + } } private synchronized Handler endHandler() { @@ -222,7 +234,6 @@ public synchronized NetSocket endHandler(Handler endHandler) { @Override public synchronized NetSocket drainHandler(Handler drainHandler) { this.drainHandler = drainHandler; - vertx.runOnContext(v -> callDrainHandler()); //If the channel is already drained, we want to call it immediately return this; } @@ -253,39 +264,77 @@ public NetSocketImpl closeHandler(Handler handler) { } @Override - public Future upgradeToSsl() { - return upgradeToSsl((String) null); + public Future upgradeToSsl(String serverName) { + return sslUpgrade(serverName, sslOptions); } @Override - public Future upgradeToSsl(String serverName) { - PromiseInternal promise = context.promise(); + public Future upgradeToSsl(SSLOptions sslOptions, String serverName) { + return sslUpgrade(serverName, sslOptions); + } + + private Future sslUpgrade(String serverName, SSLOptions sslOptions) { + if (sslOptions == null) { + return context.failedFuture("Missing SSL options"); + } + if (remoteAddress != null && !(sslOptions instanceof ClientSSLOptions)) { + return context.failedFuture("Client socket upgrade must use ClientSSLOptions"); + } else if (remoteAddress == null && !(sslOptions instanceof ServerSSLOptions)) { + return context.failedFuture("Server socket upgrade must use ServerSSLOptions"); + } if (chctx.pipeline().get("ssl") == null) { - ChannelPromise flush = chctx.newPromise(); + doPause(); + PromiseInternal flush = context.promise(); flush(flush); - flush.addListener(fut -> { - if (fut.isSuccess()) { - ChannelPromise channelPromise = chctx.newPromise(); - chctx.pipeline().addFirst("handshaker", new SslHandshakeCompletionHandler(channelPromise)); - channelPromise.addListener(promise); - ChannelHandler sslHandler; - if (remoteAddress != null) { - sslHandler = sslChannelProvider.createClientSslHandler(remoteAddress, serverName, false); + return flush + .compose(v -> { + if (sslOptions instanceof ClientSSLOptions) { + ClientSSLOptions clientSSLOptions = (ClientSSLOptions) sslOptions; + return sslHelper.resolveSslChannelProvider( + sslOptions, + clientSSLOptions.getHostnameVerificationAlgorithm(), + false, + null, + null, + context); } else { - sslHandler = sslChannelProvider.createServerHandler(); + ServerSSLOptions serverSSLOptions = (ServerSSLOptions) sslOptions; + return sslHelper.resolveSslChannelProvider( + sslOptions, + "", + serverSSLOptions.isSni(), + serverSSLOptions.getClientAuth(), + null, context); } - chctx.pipeline().addFirst("ssl", sslHandler); - } else { - promise.fail(fut.cause()); - } - }); + }) + .transform(ar -> { + Future f; + if (ar.succeeded()) { + SslChannelProvider sslChannelProvider = ar.result(); + ChannelPromise channelPromise = chctx.newPromise(); + chctx.pipeline().addFirst("handshaker", new SslHandshakeCompletionHandler(channelPromise)); + ChannelHandler sslHandler; + if (sslOptions instanceof ClientSSLOptions) { + ClientSSLOptions clientSSLOptions = (ClientSSLOptions) sslOptions; + sslHandler = sslChannelProvider.createClientSslHandler(remoteAddress, serverName, sslOptions.isUseAlpn(), clientSSLOptions.isTrustAll(), clientSSLOptions.getSslHandshakeTimeout(), clientSSLOptions.getSslHandshakeTimeoutUnit()); + } else { + sslHandler = sslChannelProvider.createServerHandler(sslOptions.isUseAlpn(), sslOptions.getSslHandshakeTimeout(), sslOptions.getSslHandshakeTimeoutUnit()); + } + chctx.pipeline().addFirst("ssl", sslHandler); + PromiseInternal p = context.promise(); + channelPromise.addListener(p); + f = p.future(); + } else { + f = context.failedFuture(ar.cause()); + } + if (!pending.isPaused()) { + doResume(); + } + return f; + }); + } else { + throw new IllegalStateException(); // ??? } - return promise.future(); - } - - @Override - protected void handleInterestedOpsChanged() { - context.emit(null, v -> callDrainHandler()); } @Override @@ -315,6 +364,19 @@ protected void handleEvent(Object evt) { } else { super.handleEvent(evt); } + if (evt instanceof ShutdownEvent) { + Handler shutdownHandler = this.shutdownHandler; + if (shutdownHandler != null) { + ShutdownEvent shutdown = (ShutdownEvent) evt; + context.emit(shutdown.timeUnit().toMillis(shutdown.timeout()), shutdownHandler); + } + } + } + + @Override + public NetSocket shutdownHandler(@Nullable Handler handler) { + shutdownHandler = handler; + return this; } private class DataMessageHandler implements Handler { @@ -324,7 +386,7 @@ public void handle(Object msg) { if (msg instanceof ByteBuf) { msg = VertxHandler.safeBuffer((ByteBuf) msg); ByteBuf byteBuf = (ByteBuf) msg; - Buffer buffer = Buffer.buffer(byteBuf); + Buffer buffer = BufferInternal.buffer(byteBuf); if (!pending.write(buffer)) { doPause(); } @@ -341,13 +403,5 @@ private void handleInvalid(Object msg) { } } } - - private synchronized void callDrainHandler() { - if (drainHandler != null) { - if (!writeQueueFull()) { - drainHandler.handle(null); - } - } - } } diff --git a/src/main/java/io/vertx/core/net/impl/OutboundMessageQueue.java b/src/main/java/io/vertx/core/net/impl/OutboundMessageQueue.java new file mode 100644 index 00000000000..a2ad45529c7 --- /dev/null +++ b/src/main/java/io/vertx/core/net/impl/OutboundMessageQueue.java @@ -0,0 +1,140 @@ +package io.vertx.core.net.impl; + +import io.netty.channel.EventLoop; +import io.vertx.core.streams.impl.OutboundWriteQueue; + +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; + +import static io.vertx.core.streams.impl.OutboundWriteQueue.numberOfUnwritableSignals; + +/** + * Outbound write queue for event-loop and channel like structures. + */ +public class OutboundMessageQueue implements Predicate { + + private final EventLoop eventLoop; + private final AtomicInteger numberOfUnwritableSignals = new AtomicInteger(); + private final OutboundWriteQueue writeQueue; + + // State accessed exclusively by the event loop thread + private boolean overflow; + + /** + * Create a queue. + * + * @param eventLoop the queue event-loop + */ + public OutboundMessageQueue(EventLoop eventLoop, Predicate predicate) { + this.eventLoop = eventLoop; + this.writeQueue = new OutboundWriteQueue<>(predicate); + } + + /** + * Create a queue. + * + * @param eventLoop the queue event-loop + */ + public OutboundMessageQueue(EventLoop eventLoop) { + this.eventLoop = eventLoop; + this.writeQueue = new OutboundWriteQueue<>(this); + } + + @Override + public boolean test(T t) { + throw new UnsupportedOperationException(); + } + + /** + * @return whether the queue is writable, this can be called from any thread + */ + public boolean isWritable() { + // Can be negative temporarily + return numberOfUnwritableSignals.get() <= 0; + } + + /** + * Write a {@code message} to the queue + * + * @param message the message to be written + * @return whether the writer can continue/stop writing to the queue + */ + public final boolean write(T message) { + boolean inEventLoop = eventLoop.inEventLoop(); + int flags; + if (inEventLoop) { + flags = writeQueue.add(message); + overflow |= (flags & OutboundWriteQueue.DRAIN_REQUIRED_MASK) != 0; + if ((flags & OutboundWriteQueue.QUEUE_WRITABLE_MASK) != 0) { + handleWriteQueueDrained(numberOfUnwritableSignals(flags)); + } + } else { + flags = writeQueue.submit(message); + if ((flags & OutboundWriteQueue.DRAIN_REQUIRED_MASK) != 0) { + eventLoop.execute(this::drainWriteQueue); + } + } + if ((flags & OutboundWriteQueue.QUEUE_UNWRITABLE_MASK) != 0) { + int val = numberOfUnwritableSignals.incrementAndGet(); + return val <= 0; + } else { + return numberOfUnwritableSignals.get() <= 0; + } + } + + /** + * Attempt to drain the queue Drain the queue. + */ + public void drain() { + assert(eventLoop.inEventLoop()); + if (overflow) { + startDraining(); + int flags = writeQueue.drain(); + overflow = (flags & OutboundWriteQueue.DRAIN_REQUIRED_MASK) != 0; + if ((flags & OutboundWriteQueue.QUEUE_WRITABLE_MASK) != 0) { + handleWriteQueueDrained(numberOfUnwritableSignals(flags)); + } + stopDraining(); + } + } + + private void drainWriteQueue() { + startDraining(); + int flags = writeQueue.drain(); + overflow = (flags & OutboundWriteQueue.DRAIN_REQUIRED_MASK) != 0; + if ((flags & OutboundWriteQueue.QUEUE_WRITABLE_MASK) != 0) { + handleWriteQueueDrained(numberOfUnwritableSignals(flags)); + } + stopDraining(); + } + + private void handleWriteQueueDrained(int numberOfSignals) { + int val = numberOfUnwritableSignals.addAndGet(-numberOfSignals); + if ((val + numberOfSignals) > 0 && val <= 0) { + writeQueueDrained(); + } + } + + /** + * Clear the queue. + * + * @return the pending writes + */ + public final List clear() { + assert(eventLoop.inEventLoop()); + return writeQueue.clear(); + } + + /** + * Called when the queue becomes writable again. + */ + protected void writeQueueDrained() { + } + + protected void startDraining() { + } + + protected void stopDraining() { + } +} diff --git a/src/main/java/io/vertx/core/net/impl/SSLHelper.java b/src/main/java/io/vertx/core/net/impl/SSLHelper.java index 1fb4fc0abed..ce8b9c41c47 100755 --- a/src/main/java/io/vertx/core/net/impl/SSLHelper.java +++ b/src/main/java/io/vertx/core/net/impl/SSLHelper.java @@ -12,24 +12,13 @@ package io.vertx.core.net.impl; import io.netty.handler.ssl.OpenSsl; -import io.netty.handler.ssl.SslProvider; import io.vertx.core.Future; import io.vertx.core.Promise; import io.vertx.core.VertxException; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.ClientAuth; import io.vertx.core.impl.ContextInternal; -import io.vertx.core.net.ClientOptionsBase; -import io.vertx.core.net.JdkSSLEngineOptions; -import io.vertx.core.net.KeyCertOptions; -import io.vertx.core.net.NetClientOptions; -import io.vertx.core.net.NetServerOptions; -import io.vertx.core.net.OpenSSLEngineOptions; -import io.vertx.core.net.SSLEngineOptions; -import io.vertx.core.net.SSLOptions; -import io.vertx.core.net.TCPSSLOptions; -import io.vertx.core.net.TrustOptions; -import io.vertx.core.spi.tls.DefaultSslContextFactory; +import io.vertx.core.net.*; import io.vertx.core.spi.tls.SslContextFactory; import javax.net.ssl.*; @@ -37,6 +26,7 @@ import java.security.cert.CRL; import java.security.cert.CertificateFactory; import java.util.*; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -47,6 +37,8 @@ */ public class SSLHelper { + private static final AtomicLong seq = new AtomicLong(); + private static final Config NULL_CONFIG = new Config(null, null, null, null, null); static final EnumMap CLIENT_AUTH_MAPPING = new EnumMap<>(ClientAuth.class); static { @@ -96,109 +88,55 @@ public static SSLEngineOptions resolveEngineOptions(SSLEngineOptions engineOptio return engineOptions; } - private final boolean ssl; - private final boolean sni; - private final boolean trustAll; - private final ClientAuth clientAuth; - private final boolean client; - private final boolean useAlpn; - private final String endpointIdentificationAlgorithm; - private final SSLEngineOptions sslEngineOptions; - private final List applicationProtocols; - private KeyManagerFactory keyManagerFactory; - private TrustManagerFactory trustManagerFactory; - private Function keyManagerFactoryMapper; - private Function trustManagerMapper; - private List crls; - private Future cachedProvider; + private final Supplier supplier; + private final boolean useWorkerPool; + private final Map> configMap; + private final Map> sslChannelProviderMap; - public SSLHelper(TCPSSLOptions options, List applicationProtocols) { - this.sslEngineOptions = options.getSslEngineOptions(); - this.ssl = options.isSsl(); - this.useAlpn = options.isUseAlpn(); - this.client = options instanceof ClientOptionsBase; - this.trustAll = options instanceof ClientOptionsBase && ((ClientOptionsBase)options).isTrustAll(); - this.clientAuth = options instanceof NetServerOptions ? ((NetServerOptions)options).getClientAuth() : ClientAuth.NONE; - this.endpointIdentificationAlgorithm = options instanceof NetClientOptions ? ((NetClientOptions)options).getHostnameVerificationAlgorithm() : ""; - this.sni = options instanceof NetServerOptions && ((NetServerOptions) options).isSni(); - this.applicationProtocols = applicationProtocols; + public SSLHelper(SSLEngineOptions sslEngineOptions, int cacheMaxSize) { + this.configMap = new LruCache<>(cacheMaxSize); + this.sslChannelProviderMap = new LruCache<>(cacheMaxSize); + this.supplier = sslEngineOptions::sslContextFactory; + this.useWorkerPool = sslEngineOptions.getUseWorkerThread(); } - private static class CachedProvider { - final SSLOptions options; - final SslChannelProvider sslChannelProvider; - final Throwable failure; - CachedProvider(SSLOptions options, SslChannelProvider sslChannelProvider, Throwable failure) { - this.options = options; - this.sslChannelProvider = sslChannelProvider; - this.failure = failure; + public synchronized int sniEntrySize() { + int size = 0; + for (Future fut : sslChannelProviderMap.values()) { + SslChannelProvider result = fut.result(); + if (result != null) { + size += result.sniEntrySize(); + } } + return size; } - private class EngineConfig { - - private final SSLOptions sslOptions; - private final Supplier supplier; - private final boolean useWorkerPool; - - public EngineConfig(SSLOptions sslOptions, Supplier supplier, boolean useWorkerPool) { - this.sslOptions = sslOptions; - this.supplier = supplier; - this.useWorkerPool = useWorkerPool; - } + public SSLHelper(SSLEngineOptions sslEngineOptions) { + this(sslEngineOptions, 256); + } - SslContextProvider sslContextProvider() { - return new SslContextProvider( - clientAuth, - endpointIdentificationAlgorithm, - applicationProtocols, - sslOptions.getEnabledCipherSuites(), - sslOptions.getEnabledSecureTransportProtocols(), - keyManagerFactory, - keyManagerFactoryMapper, - trustManagerFactory, - trustManagerMapper, - crls, - supplier); - } + public Future resolveSslChannelProvider(SSLOptions options, String endpointIdentificationAlgorithm, boolean useSNI, ClientAuth clientAuth, List applicationProtocols, ContextInternal ctx) { + return resolveSslChannelProvider(options, endpointIdentificationAlgorithm, useSNI, clientAuth, applicationProtocols, false, ctx); } - /** - * Update cached options. This method ensures updates are serialized a nd performed when options is different - * (based on {@code equals}). Updates only happen when transforming {@code options} to a {@link SslChannelProvider} - * succeeds. - * - * @param options the options to use - * @param ctx the vertx context - * @return a future of the resolved channel provider - */ - public Future updateSslContext(SSLOptions options, ContextInternal ctx) { + public Future resolveSslChannelProvider(SSLOptions options, String endpointIdentificationAlgorithm, boolean useSNI, ClientAuth clientAuth, List applicationProtocols, boolean force, ContextInternal ctx) { + Promise promise; + ConfigKey k = new ConfigKey(options); synchronized (this) { - if (cachedProvider == null) { - cachedProvider = this.buildChannelProvider(options, ctx).map(a -> new CachedProvider(options, a, null)); + if (force) { + sslChannelProviderMap.remove(k); } else { - cachedProvider = cachedProvider.transform(prev -> { - if (prev.succeeded() && prev.result().options.equals(options)) { - return Future.succeededFuture(prev.result()); - } else { - return this - .buildChannelProvider(options, ctx) - .transform(ar -> { - if (ar.succeeded()) { - return ctx.succeededFuture(new CachedProvider(options, ar.result(), null)); - } else { - if (prev.succeeded()) { - return ctx.succeededFuture(new CachedProvider(prev.result().options, prev.result().sslChannelProvider, ar.cause())); - } else { - return ctx.failedFuture(prev.cause()); - } - } - }); - } - }); + Future v = sslChannelProviderMap.get(k); + if (v != null) { + return v; + } } - return cachedProvider.map(c -> new SslContextUpdate(c.sslChannelProvider, c.failure)); + promise = Promise.promise(); + sslChannelProviderMap.put(k, promise.future()); } + buildChannelProvider(options, endpointIdentificationAlgorithm, useSNI, clientAuth, applicationProtocols, force, ctx) + .onComplete(promise); + return promise.future(); } /** @@ -207,8 +145,28 @@ public Future updateSslContext(SSLOptions options, ContextInte * @param ctx the context * @return a future resolved when the helper is initialized */ - public Future buildContextProvider(SSLOptions sslOptions, ContextInternal ctx) { - return build(new SSLOptions(sslOptions), ctx).map(EngineConfig::sslContextProvider); + Future buildContextProvider(SSLOptions sslOptions, + String endpointIdentificationAlgorithm, + ClientAuth clientAuth, + List applicationProtocols, + boolean force, + ContextInternal ctx) { + return buildConfig(sslOptions, force, ctx).map(config -> buildSslContextProvider(sslOptions, endpointIdentificationAlgorithm, supplier, clientAuth, applicationProtocols, config)); + } + + private SslContextProvider buildSslContextProvider(SSLOptions sslOptions, String endpointIdentificationAlgorithm, Supplier supplier, ClientAuth clientAuth, List applicationProtocols, Config config) { + return new SslContextProvider( + clientAuth, + endpointIdentificationAlgorithm, + applicationProtocols, + sslOptions.getEnabledCipherSuites(), + sslOptions.getEnabledSecureTransportProtocols(), + config.keyManagerFactory, + config.keyManagerFactoryMapper, + config.trustManagerFactory, + config.trustManagerMapper, + config.crls, + supplier); } /** @@ -217,80 +175,140 @@ public Future buildContextProvider(SSLOptions sslOptions, Co * @param ctx the context * @return a future resolved when the helper is initialized */ - public Future buildChannelProvider(SSLOptions sslOptions, ContextInternal ctx) { - return build(new SSLOptions(sslOptions), ctx).map(c -> new SslChannelProvider( - c.sslContextProvider(), c.sslOptions.getSslHandshakeTimeout(), c.sslOptions.getSslHandshakeTimeoutUnit(), sni, - trustAll, - useAlpn, + protected Future buildChannelProvider(SSLOptions sslOptions, + String endpointIdentificationAlgorithm, + boolean useSNI, + ClientAuth clientAuth, + List applicationProtocols, + boolean force, + ContextInternal ctx) { + Future f; + boolean useWorker; + f = buildConfig(sslOptions, force, ctx).map(config -> buildSslContextProvider(sslOptions, + endpointIdentificationAlgorithm, supplier, clientAuth, applicationProtocols, config)); + useWorker = useWorkerPool; + return f.map(c -> new SslChannelProvider( + c, + useSNI, ctx.owner().getInternalWorkerPool().executor(), - c.useWorkerPool - )); + useWorker)); } - /** - * Initialize the helper, this loads and validates the configuration. - * - * @param ctx the context - * @return a future resolved when the helper is initialized - */ - private Future build(SSLOptions sslOptions, ContextInternal ctx) { - Future sslContextFactorySupplier; - KeyCertOptions keyCertOptions = sslOptions.getKeyCertOptions(); - TrustOptions trustOptions = sslOptions.getTrustOptions(); - if (keyCertOptions != null || trustOptions != null || trustAll || ssl) { - Promise promise = Promise.promise(); - sslContextFactorySupplier = promise.future(); - ctx.executeBlockingInternal(p -> { - try { - if (sslOptions.getKeyCertOptions() != null) { - keyManagerFactory = sslOptions.getKeyCertOptions().getKeyManagerFactory(ctx.owner()); - keyManagerFactoryMapper = sslOptions.getKeyCertOptions().keyManagerFactoryMapper(ctx.owner()); - } - if (sslOptions.getTrustOptions() != null) { - trustManagerFactory = sslOptions.getTrustOptions().getTrustManagerFactory(ctx.owner()); - trustManagerMapper = sslOptions.getTrustOptions().trustManagerMapper(ctx.owner()); - } - crls = new ArrayList<>(); - List tmp = new ArrayList<>(); - if (sslOptions.getCrlPaths() != null) { - tmp.addAll(sslOptions.getCrlPaths() - .stream() - .map(path -> ctx.owner().resolveFile(path).getAbsolutePath()) - .map(ctx.owner().fileSystem()::readFileBlocking) - .collect(Collectors.toList())); - } - if (sslOptions.getCrlValues() != null) { - tmp.addAll(sslOptions.getCrlValues()); - } - CertificateFactory certificatefactory = CertificateFactory.getInstance("X.509"); - for (Buffer crlValue : tmp) { - crls.addAll(certificatefactory.generateCRLs(new ByteArrayInputStream(crlValue.getBytes()))); - } - } catch (Exception e) { - p.fail(e); - return; - } - if (client || sslOptions.getKeyCertOptions() != null) { - p.complete(); - } else { - p.fail("Key/certificate is mandatory for SSL"); - } - }).compose(v2 -> ctx.executeBlockingInternal(p -> { - Supplier supplier; - boolean useWorkerPool; - try { - SSLEngineOptions resolvedEngineOptions = resolveEngineOptions(sslEngineOptions, useAlpn); - supplier = resolvedEngineOptions::sslContextFactory; - useWorkerPool = resolvedEngineOptions.getUseWorkerThread(); - } catch (Exception e) { - p.fail(e); - return; + private Future buildConfig(SSLOptions sslOptions, boolean force, ContextInternal ctx) { + if (sslOptions.getTrustOptions() == null && sslOptions.getKeyCertOptions() == null) { + return Future.succeededFuture(NULL_CONFIG); + } + Promise promise; + ConfigKey k = new ConfigKey(sslOptions); + synchronized (this) { + if (force) { + configMap.remove(k); + } else { + Future fut = configMap.get(k); + if (fut != null) { + return fut; } - p.complete(new EngineConfig(sslOptions, supplier, useWorkerPool)); - })).onComplete(promise); - } else { - sslContextFactorySupplier = Future.succeededFuture(new EngineConfig(sslOptions, () -> new DefaultSslContextFactory(SslProvider.JDK, false), SSLEngineOptions.DEFAULT_USE_WORKER_POOL)); + } + promise = Promise.promise(); + configMap.put(k, promise.future()); + } + ctx.executeBlockingInternal(() -> { + KeyManagerFactory keyManagerFactory = null; + Function keyManagerFactoryMapper = null; + TrustManagerFactory trustManagerFactory = null; + Function trustManagerMapper = null; + List crls = new ArrayList<>(); + if (sslOptions.getKeyCertOptions() != null) { + keyManagerFactory = sslOptions.getKeyCertOptions().getKeyManagerFactory(ctx.owner()); + keyManagerFactoryMapper = sslOptions.getKeyCertOptions().keyManagerFactoryMapper(ctx.owner()); + } + if (sslOptions.getTrustOptions() != null) { + trustManagerFactory = sslOptions.getTrustOptions().getTrustManagerFactory(ctx.owner()); + trustManagerMapper = sslOptions.getTrustOptions().trustManagerMapper(ctx.owner()); + } + List tmp = new ArrayList<>(); + if (sslOptions.getCrlPaths() != null) { + tmp.addAll(sslOptions.getCrlPaths() + .stream() + .map(path -> ctx.owner().resolveFile(path).getAbsolutePath()) + .map(ctx.owner().fileSystem()::readFileBlocking) + .collect(Collectors.toList())); + } + if (sslOptions.getCrlValues() != null) { + tmp.addAll(sslOptions.getCrlValues()); + } + CertificateFactory certificatefactory = CertificateFactory.getInstance("X.509"); + for (Buffer crlValue : tmp) { + crls.addAll(certificatefactory.generateCRLs(new ByteArrayInputStream(crlValue.getBytes()))); + } + return new Config(keyManagerFactory, trustManagerFactory, keyManagerFactoryMapper, trustManagerMapper, crls); + }).onComplete(promise); + return promise.future(); + } + + private static class LruCache extends LinkedHashMap { + + private final int maxSize; + + public LruCache(int maxSize) { + if (maxSize < 1) { + throw new UnsupportedOperationException(); + } + this.maxSize = maxSize; + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > maxSize; + } + } + + private final static class ConfigKey { + private final KeyCertOptions keyCertOptions; + private final TrustOptions trustOptions; + private final List crlValues; + public ConfigKey(SSLOptions options) { + this(options.getKeyCertOptions(), options.getTrustOptions(), options.getCrlValues()); + } + public ConfigKey(KeyCertOptions keyCertOptions, TrustOptions trustOptions, List crlValues) { + this.keyCertOptions = keyCertOptions; + this.trustOptions = trustOptions; + this.crlValues = crlValues != null ? new ArrayList<>(crlValues) : null; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof ConfigKey) { + ConfigKey that = (ConfigKey) obj; + return Objects.equals(keyCertOptions, that.keyCertOptions) && Objects.equals(trustOptions, that.trustOptions) && Objects.equals(crlValues, that.crlValues); + } + return false; + } + + @Override + public int hashCode() { + int hashCode = Objects.hashCode(keyCertOptions); + hashCode = 31 * hashCode + Objects.hashCode(trustOptions); + hashCode = 31 * hashCode + Objects.hashCode(crlValues); + return hashCode; + } + } + + private final static class Config { + private final KeyManagerFactory keyManagerFactory; + private final TrustManagerFactory trustManagerFactory; + private final Function keyManagerFactoryMapper; + private final Function trustManagerMapper; + private final List crls; + public Config(KeyManagerFactory keyManagerFactory, TrustManagerFactory trustManagerFactory, Function keyManagerFactoryMapper, Function trustManagerMapper, List crls) { + this.keyManagerFactory = keyManagerFactory; + this.trustManagerFactory = trustManagerFactory; + this.keyManagerFactoryMapper = keyManagerFactoryMapper; + this.trustManagerMapper = trustManagerMapper; + this.crls = crls; } - return sslContextFactorySupplier; } } diff --git a/src/main/java/io/vertx/core/net/impl/SslChannelProvider.java b/src/main/java/io/vertx/core/net/impl/SslChannelProvider.java index 3c84d978bac..e13acd8ba0b 100644 --- a/src/main/java/io/vertx/core/net/impl/SslChannelProvider.java +++ b/src/main/java/io/vertx/core/net/impl/SslChannelProvider.java @@ -17,6 +17,7 @@ import io.netty.handler.ssl.SslHandler; import io.netty.util.AsyncMapping; import io.netty.util.concurrent.ImmediateExecutor; +import io.vertx.core.VertxException; import io.vertx.core.net.SocketAddress; import javax.net.ssl.KeyManagerFactory; @@ -33,13 +34,9 @@ */ public class SslChannelProvider { - private final long sslHandshakeTimeout; - private final TimeUnit sslHandshakeTimeoutUnit; private final Executor workerPool; private final boolean useWorkerPool; private final boolean sni; - private final boolean useAlpn; - private final boolean trustAll; private final SslContextProvider sslContextProvider; private final SslContext[] sslContexts = new SslContext[2]; private final Map[] sslContextMaps = new Map[]{ @@ -47,46 +44,53 @@ public class SslChannelProvider { }; public SslChannelProvider(SslContextProvider sslContextProvider, - long sslHandshakeTimeout, - TimeUnit sslHandshakeTimeoutUnit, boolean sni, - boolean trustAll, - boolean useAlpn, Executor workerPool, boolean useWorkerPool) { this.workerPool = workerPool; this.useWorkerPool = useWorkerPool; - this.useAlpn = useAlpn; this.sni = sni; - this.trustAll = trustAll; - this.sslHandshakeTimeout = sslHandshakeTimeout; - this.sslHandshakeTimeoutUnit = sslHandshakeTimeoutUnit; this.sslContextProvider = sslContextProvider; } + public int sniEntrySize() { + return sslContextMaps[0].size() + sslContextMaps[1].size(); + } + public SslContextProvider sslContextProvider() { return sslContextProvider; } - public SslContext sslClientContext(String serverName, boolean useAlpn) { - return sslClientContext(serverName, useAlpn, trustAll); + public SslContext sslClientContext(String serverName, boolean useAlpn, boolean trustAll) { + try { + return sslContext(serverName, useAlpn, false, trustAll); + } catch (Exception e) { + throw new VertxException(e); + } } - public SslContext sslClientContext(String serverName, boolean useAlpn, boolean trustAll) { + public SslContext sslContext(String serverName, boolean useAlpn, boolean server, boolean trustAll) throws Exception { int idx = idx(useAlpn); + if (serverName != null) { + KeyManagerFactory kmf = sslContextProvider.resolveKeyManagerFactory(serverName); + TrustManager[] trustManagers = trustAll ? null : sslContextProvider.resolveTrustManagers(serverName); + if (kmf != null || trustManagers != null || !server) { + return sslContextMaps[idx].computeIfAbsent(serverName, s -> sslContextProvider.createContext(server, kmf, trustManagers, s, useAlpn, trustAll)); + } + } if (sslContexts[idx] == null) { - SslContext context = sslContextProvider.createClientContext(serverName, useAlpn, trustAll); + SslContext context = sslContextProvider.createContext(server, null, null, serverName, useAlpn, trustAll); sslContexts[idx] = context; } return sslContexts[idx]; } public SslContext sslServerContext(boolean useAlpn) { - int idx = idx(useAlpn); - if (sslContexts[idx] == null) { - sslContexts[idx] = sslContextProvider.createServerContext(useAlpn); + try { + return sslContext(null, useAlpn, true, false); + } catch (Exception e) { + throw new VertxException(e); } - return sslContexts[idx]; } /** @@ -94,57 +98,44 @@ public SslContext sslServerContext(boolean useAlpn) { * * @return the {@link AsyncMapping} */ - public AsyncMapping serverNameMapping() { + public AsyncMapping serverNameMapping(boolean useAlpn) { return (AsyncMapping) (serverName, promise) -> { workerPool.execute(() -> { - if (serverName == null) { - promise.setSuccess(sslServerContext(useAlpn)); - } else { - KeyManagerFactory kmf; - try { - kmf = sslContextProvider.resolveKeyManagerFactory(serverName); - } catch (Exception e) { - promise.setFailure(e); - return; - } - TrustManager[] trustManagers; - try { - trustManagers = sslContextProvider.resolveTrustManagers(serverName); - } catch (Exception e) { - promise.setFailure(e); - return; - } - int idx = idx(useAlpn); - SslContext sslContext = sslContextMaps[idx].computeIfAbsent(serverName, s -> sslContextProvider.createServerContext(kmf, trustManagers, s, useAlpn)); - promise.setSuccess(sslContext); + SslContext sslContext; + try { + sslContext = sslContext(serverName, useAlpn, true, false); + } catch (Exception e) { + promise.setFailure(e); + return; } + promise.setSuccess(sslContext); }); return promise; }; } - public SslHandler createClientSslHandler(SocketAddress remoteAddress, String serverName, boolean useAlpn) { - SslContext sslContext = sslClientContext(serverName, useAlpn); + public SslHandler createClientSslHandler(SocketAddress peerAddress, String serverName, boolean useAlpn, boolean trustAll, long sslHandshakeTimeout, TimeUnit sslHandshakeTimeoutUnit) { + SslContext sslContext = sslClientContext(serverName, useAlpn, trustAll); SslHandler sslHandler; Executor delegatedTaskExec = useWorkerPool ? workerPool : ImmediateExecutor.INSTANCE; - if (remoteAddress.isDomainSocket()) { - sslHandler = sslContext.newHandler(ByteBufAllocator.DEFAULT, delegatedTaskExec); + if (peerAddress != null && peerAddress.isInetSocket()) { + sslHandler = sslContext.newHandler(ByteBufAllocator.DEFAULT, peerAddress.host(), peerAddress.port(), delegatedTaskExec); } else { - sslHandler = sslContext.newHandler(ByteBufAllocator.DEFAULT, remoteAddress.host(), remoteAddress.port(), delegatedTaskExec); + sslHandler = sslContext.newHandler(ByteBufAllocator.DEFAULT, delegatedTaskExec); } sslHandler.setHandshakeTimeout(sslHandshakeTimeout, sslHandshakeTimeoutUnit); return sslHandler; } - public ChannelHandler createServerHandler() { + public ChannelHandler createServerHandler(boolean useAlpn, long sslHandshakeTimeout, TimeUnit sslHandshakeTimeoutUnit) { if (sni) { - return createSniHandler(); + return createSniHandler(useAlpn, sslHandshakeTimeout, sslHandshakeTimeoutUnit); } else { - return createServerSslHandler(useAlpn); + return createServerSslHandler(useAlpn, sslHandshakeTimeout, sslHandshakeTimeoutUnit); } } - private SslHandler createServerSslHandler(boolean useAlpn) { + private SslHandler createServerSslHandler(boolean useAlpn, long sslHandshakeTimeout, TimeUnit sslHandshakeTimeoutUnit) { SslContext sslContext = sslServerContext(useAlpn); Executor delegatedTaskExec = useWorkerPool ? workerPool : ImmediateExecutor.INSTANCE; SslHandler sslHandler = sslContext.newHandler(ByteBufAllocator.DEFAULT, delegatedTaskExec); @@ -152,9 +143,9 @@ private SslHandler createServerSslHandler(boolean useAlpn) { return sslHandler; } - private SniHandler createSniHandler() { + private SniHandler createSniHandler(boolean useAlpn, long sslHandshakeTimeout, TimeUnit sslHandshakeTimeoutUnit) { Executor delegatedTaskExec = useWorkerPool ? workerPool : ImmediateExecutor.INSTANCE; - return new VertxSniHandler(serverNameMapping(), sslHandshakeTimeoutUnit.toMillis(sslHandshakeTimeout), delegatedTaskExec); + return new VertxSniHandler(serverNameMapping(useAlpn), sslHandshakeTimeoutUnit.toMillis(sslHandshakeTimeout), delegatedTaskExec); } private static int idx(boolean useAlpn) { diff --git a/src/main/java/io/vertx/core/net/impl/SslContextProvider.java b/src/main/java/io/vertx/core/net/impl/SslContextProvider.java index f60c0ce369b..27efd461713 100644 --- a/src/main/java/io/vertx/core/net/impl/SslContextProvider.java +++ b/src/main/java/io/vertx/core/net/impl/SslContextProvider.java @@ -66,7 +66,36 @@ public SslContextProvider(ClientAuth clientAuth, this.crls = crls; } - public VertxSslContext createClientContext(String serverName, boolean useAlpn, boolean trustAll) { + public VertxSslContext createContext(boolean server, + KeyManagerFactory keyManagerFactory, + TrustManager[] trustManagers, + String serverName, + boolean useAlpn, + boolean trustAll) { + if (keyManagerFactory == null) { + keyManagerFactory = defaultKeyManagerFactory(); + } + if (trustAll) { + trustManagers = SslContextProvider.createTrustAllManager(); + } else if (trustManagers == null) { + trustManagers = defaultTrustManagers(); + } + if (server) { + return createServerContext(keyManagerFactory, trustManagers, serverName, useAlpn); + } else { + return createClientContext(keyManagerFactory, trustManagers, serverName, useAlpn); + } + } + + public VertxSslContext createContext(boolean server, boolean useAlpn) { + return createContext(server, defaultKeyManagerFactory(), defaultTrustManagers(), null, useAlpn, false); + } + + public VertxSslContext createClientContext( + KeyManagerFactory keyManagerFactory, + TrustManager[] trustManagers, + String serverName, + boolean useAlpn) { try { SslContextFactory factory = provider.get() .useAlpn(useAlpn) @@ -76,12 +105,6 @@ public VertxSslContext createClientContext(String serverName, boolean useAlpn, b if (keyManagerFactory != null) { factory.keyMananagerFactory(keyManagerFactory); } - TrustManager[] trustManagers = null; - if (trustAll) { - trustManagers = new TrustManager[] { createTrustAllTrustManager() }; - } else if (trustManagerFactory != null) { - trustManagers = trustManagerFactory.getTrustManagers(); - } if (trustManagers != null) { TrustManagerFactory tmf = buildVertxTrustManagerFactory(trustManagers); factory.trustManagerFactory(tmf); @@ -98,10 +121,6 @@ protected void initEngine(SSLEngine engine) { } } - public VertxSslContext createServerContext(boolean useAlpn) { - return createServerContext(keyManagerFactory, trustManagerFactory != null ? trustManagerFactory.getTrustManagers() : null, null, useAlpn); - } - public VertxSslContext createServerContext(KeyManagerFactory keyManagerFactory, TrustManager[] trustManagers, String serverName, @@ -135,16 +154,20 @@ protected void initEngine(SSLEngine engine) { } } - public KeyManagerFactory loadKeyManagerFactory(String serverName) throws Exception { - if (keyManagerFactoryMapper != null) { - return keyManagerFactoryMapper.apply(serverName); - } - return null; + public TrustManager[] defaultTrustManagers() { + return trustManagerFactory != null ? trustManagerFactory.getTrustManagers() : null; + } + + public TrustManagerFactory defaultTrustManagerFactory() { + return trustManagerFactory; + } + + public KeyManagerFactory defaultKeyManagerFactory() { + return keyManagerFactory; } /** - * Resolve the {@link KeyManagerFactory} for the {@code serverName}, when a factory cannot be resolved, the default - * factory is returned. + * Resolve the {@link KeyManagerFactory} for the {@code serverName}, when a factory cannot be resolved, {@code null} is returned. *
* This can block and should be executed on the appropriate thread. * @@ -153,23 +176,14 @@ public KeyManagerFactory loadKeyManagerFactory(String serverName) throws Excepti * @throws Exception anything that would prevent loading the factory */ public KeyManagerFactory resolveKeyManagerFactory(String serverName) throws Exception { - KeyManagerFactory kmf = loadKeyManagerFactory(serverName); - if (kmf == null) { - kmf = keyManagerFactory; - } - return kmf; - } - - public TrustManager[] loadTrustManagers(String serverName) throws Exception { - if (trustManagerMapper != null) { - return trustManagerMapper.apply(serverName); + if (keyManagerFactoryMapper != null) { + return keyManagerFactoryMapper.apply(serverName); } return null; } /** - * Resolve the {@link TrustManager}[] for the {@code serverName}, when managers cannot be resolved, the default - * managers are returned. + * Resolve the {@link TrustManager}[] for the {@code serverName}, when managers cannot be resolved, {@code null} is returned. *
* This can block and should be executed on the appropriate thread. * @@ -178,11 +192,10 @@ public TrustManager[] loadTrustManagers(String serverName) throws Exception { * @throws Exception anything that would prevent loading the managers */ public TrustManager[] resolveTrustManagers(String serverName) throws Exception { - TrustManager[] trustManagers = loadTrustManagers(serverName); - if (trustManagers == null && trustManagerFactory != null) { - trustManagers = trustManagerFactory.getTrustManagers(); + if (trustManagerMapper != null) { + return trustManagerMapper.apply(serverName); } - return trustManagers; + return null; } private VertxTrustManagerFactory buildVertxTrustManagerFactory(TrustManager[] mgrs) { @@ -232,29 +245,31 @@ public X509Certificate[] getAcceptedIssuers() { return trustMgrs; } - // Create a TrustManager which trusts everything - private static TrustManager createTrustAllTrustManager() { - return new X509TrustManager() { - @Override - public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { - } + private static final TrustManager TRUST_ALL_MANAGER = new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { + } - @Override - public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { - } + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { + } - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - }; + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + }; + + // Create a TrustManager which trusts everything + private static TrustManager[] createTrustAllManager() { + return new TrustManager[] { TRUST_ALL_MANAGER }; } public void configureEngine(SSLEngine engine, Set enabledProtocols, String serverName, boolean client) { Set protocols = new LinkedHashSet<>(enabledProtocols); protocols.retainAll(Arrays.asList(engine.getSupportedProtocols())); engine.setEnabledProtocols(protocols.toArray(new String[protocols.size()])); - if (client && !endpointIdentificationAlgorithm.isEmpty()) { + if (client && endpointIdentificationAlgorithm != null && !endpointIdentificationAlgorithm.isEmpty()) { SSLParameters sslParameters = engine.getSSLParameters(); sslParameters.setEndpointIdentificationAlgorithm(endpointIdentificationAlgorithm); engine.setSSLParameters(sslParameters); diff --git a/src/main/java/io/vertx/core/net/impl/SslContextUpdate.java b/src/main/java/io/vertx/core/net/impl/SslContextUpdate.java index 8369efd11e9..4579f798103 100644 --- a/src/main/java/io/vertx/core/net/impl/SslContextUpdate.java +++ b/src/main/java/io/vertx/core/net/impl/SslContextUpdate.java @@ -16,10 +16,12 @@ public class SslContextUpdate { private final SslChannelProvider sslChannelProvider; + private final boolean updated; private final Throwable error; - SslContextUpdate(SslChannelProvider sslChannelProvider, Throwable error) { + SslContextUpdate(SslChannelProvider sslChannelProvider, boolean updated, Throwable error) { this.sslChannelProvider = sslChannelProvider; + this.updated = updated; this.error = error; } @@ -30,6 +32,13 @@ public SslChannelProvider sslChannelProvider() { return sslChannelProvider; } + /** + * @return whether the update occurred + */ + public boolean isUpdated() { + return updated; + } + /** * @return the optional error of the update operation */ diff --git a/src/main/java/io/vertx/core/net/impl/TCPServerBase.java b/src/main/java/io/vertx/core/net/impl/TCPServerBase.java index 4aa1bd081c0..fc738f3883c 100644 --- a/src/main/java/io/vertx/core/net/impl/TCPServerBase.java +++ b/src/main/java/io/vertx/core/net/impl/TCPServerBase.java @@ -26,16 +26,13 @@ import io.vertx.core.Handler; import io.vertx.core.Promise; import io.vertx.core.buffer.impl.PartialPooledByteBufAllocator; -import io.vertx.core.impl.AddressResolver; +import io.vertx.core.impl.HostnameResolver; import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.future.PromiseInternal; import io.vertx.core.impl.VertxInternal; import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; -import io.vertx.core.net.NetServerOptions; -import io.vertx.core.net.SSLOptions; -import io.vertx.core.net.SocketAddress; -import io.vertx.core.net.TrafficShapingOptions; +import io.vertx.core.net.*; import io.vertx.core.spi.metrics.MetricsProvider; import io.vertx.core.spi.metrics.TCPMetrics; @@ -44,7 +41,6 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.function.BiConsumer; /** * Base class for TCP servers @@ -62,7 +58,7 @@ public abstract class TCPServerBase implements Closeable, MetricsProvider { // Per server private EventLoop eventLoop; - private BiConsumer childHandler; + private Worker childHandler; private Handler worker; private volatile boolean listening; private ContextInternal listenContext; @@ -70,7 +66,8 @@ public abstract class TCPServerBase implements Closeable, MetricsProvider { // Main private SSLHelper sslHelper; - private volatile Future sslChannelProvider; + private volatile Future sslChannelProvider; + private Future updateInProgress; private GlobalTrafficShapingHandler trafficShapingHandler; private ServerChannelLoadBalancer channelBalancer; private Future bindFuture; @@ -80,17 +77,12 @@ public abstract class TCPServerBase implements Closeable, MetricsProvider { public TCPServerBase(VertxInternal vertx, NetServerOptions options) { this.vertx = vertx; - this.options = new NetServerOptions(options); + this.options = options.copy(); this.creatingContext = vertx.getContext(); } public SslContextProvider sslContextProvider() { - SslContextUpdate update = sslChannelProvider.result(); - if (update != null) { - return update.sslChannelProvider().sslContextProvider(); - } else { - return null; - } + return sslChannelProvider.result().sslContextProvider(); } public int actualPort() { @@ -98,12 +90,12 @@ public int actualPort() { return server != null ? server.actualPort : actualPort; } - protected abstract BiConsumer childHandler(ContextInternal context, SocketAddress socketAddress, GlobalTrafficShapingHandler trafficShapingHandler); - - protected SSLHelper createSSLHelper() { - return new SSLHelper(options, null); + public interface Worker { + void accept(Channel ch, SslChannelProvider sslChannelProvider, SSLHelper sslHelper, ServerSSLOptions sslOptions); } + protected abstract Worker childHandler(ContextInternal context, SocketAddress socketAddress, GlobalTrafficShapingHandler trafficShapingHandler); + protected GlobalTrafficShapingHandler createTrafficShapingHandler() { return createTrafficShapingHandler(vertx.getEventLoopGroup(), options.getTrafficShapingOptions()); } @@ -113,15 +105,13 @@ private GlobalTrafficShapingHandler createTrafficShapingHandler(EventLoopGroup e return null; } GlobalTrafficShapingHandler trafficShapingHandler; - if (options.getMaxDelayToWait() != 0 && options.getCheckIntervalForStats() != 0) { - long maxDelayToWaitInSeconds = options.getMaxDelayToWaitTimeUnit().toSeconds(options.getMaxDelayToWait()); - long checkIntervalForStatsInSeconds = options.getCheckIntervalForStatsTimeUnit().toSeconds(options.getCheckIntervalForStats()); - trafficShapingHandler = new GlobalTrafficShapingHandler(eventLoopGroup, options.getOutboundGlobalBandwidth(), options.getInboundGlobalBandwidth(), checkIntervalForStatsInSeconds, maxDelayToWaitInSeconds); - } else if (options.getCheckIntervalForStats() != 0) { - long checkIntervalForStatsInSeconds = options.getCheckIntervalForStatsTimeUnit().toSeconds(options.getCheckIntervalForStats()); - trafficShapingHandler = new GlobalTrafficShapingHandler(eventLoopGroup, options.getOutboundGlobalBandwidth(), options.getInboundGlobalBandwidth(), checkIntervalForStatsInSeconds); + if (options.getMaxDelayToWait() != 0) { + long maxDelayToWaitInMillis = options.getMaxDelayToWaitTimeUnit().toMillis(options.getMaxDelayToWait()); + long checkIntervalForStatsInMillis = options.getCheckIntervalForStatsTimeUnit().toMillis(options.getCheckIntervalForStats()); + trafficShapingHandler = new GlobalTrafficShapingHandler(eventLoopGroup, options.getOutboundGlobalBandwidth(), options.getInboundGlobalBandwidth(), checkIntervalForStatsInMillis, maxDelayToWaitInMillis); } else { - trafficShapingHandler = new GlobalTrafficShapingHandler(eventLoopGroup, options.getOutboundGlobalBandwidth(), options.getInboundGlobalBandwidth()); + long checkIntervalForStatsInMillis = options.getCheckIntervalForStatsTimeUnit().toMillis(options.getCheckIntervalForStats()); + trafficShapingHandler = new GlobalTrafficShapingHandler(eventLoopGroup, options.getOutboundGlobalBandwidth(), options.getInboundGlobalBandwidth(), checkIntervalForStatsInMillis); } if (options.getPeakOutboundGlobalBandwidth() != 0) { trafficShapingHandler.setMaxGlobalWriteSize(options.getPeakOutboundGlobalBandwidth()); @@ -129,23 +119,73 @@ private GlobalTrafficShapingHandler createTrafficShapingHandler(EventLoopGroup e return trafficShapingHandler; } - public Future updateSSLOptions(SSLOptions options) { + protected void configure(SSLOptions options) { + } + + public int sniEntrySize() { + return sslHelper.sniEntrySize(); + } + + public Future updateSSLOptions(ServerSSLOptions options, boolean force) { TCPServerBase server = actualServer; if (server != null && server != this) { - return server.updateSSLOptions(options); + return server.updateSSLOptions(options, force); } else { ContextInternal ctx = vertx.getOrCreateContext(); - Future update = sslHelper.updateSslContext(new SSLOptions(options), ctx); - sslChannelProvider = update; - return update.transform(ar -> { - if (ar.failed()) { - return ctx.failedFuture(ar.cause()); - } else if (ar.succeeded() && ar.result().error() != null) { - return ctx.failedFuture(ar.result().error()); + Future fut; + SslChannelProvider current; + synchronized (this) { + current = sslChannelProvider.result(); + if (updateInProgress == null) { + ServerSSLOptions sslOptions = options.copy(); + configure(sslOptions); + updateInProgress = sslHelper.resolveSslChannelProvider( + sslOptions, + null, + sslOptions.isSni(), + sslOptions.getClientAuth(), + sslOptions.getApplicationLayerProtocols(), + force, + ctx); + fut = updateInProgress; } else { - return ctx.succeededFuture(); + return updateInProgress.mapEmpty().transform(ar -> updateSSLOptions(options, force)); + } + } + fut.onComplete(ar -> { + synchronized (this) { + updateInProgress = null; + if (ar.succeeded()) { + sslChannelProvider = fut; + } } }); + return fut.map(res -> res != current); + } + } + + public void updateTrafficShapingOptions(TrafficShapingOptions options) { + if (options == null) { + throw new IllegalArgumentException("Invalid null value passed for traffic shaping options update"); + } + if (trafficShapingHandler == null) { + throw new IllegalStateException("Unable to update traffic shaping options because the server was not configured " + + "to use traffic shaping during startup"); + } + TCPServerBase server = actualServer; + if (server != null && server != this) { + server.updateTrafficShapingOptions(options); + } else { + long checkIntervalForStatsInMillis = options.getCheckIntervalForStatsTimeUnit().toMillis(options.getCheckIntervalForStats()); + trafficShapingHandler.configure(options.getOutboundGlobalBandwidth(), options.getInboundGlobalBandwidth(), checkIntervalForStatsInMillis); + + if (options.getPeakOutboundGlobalBandwidth() != 0) { + trafficShapingHandler.setMaxGlobalWriteSize(options.getPeakOutboundGlobalBandwidth()); + } + if (options.getMaxDelayToWait() != 0) { + long maxDelayToWaitInMillis = options.getMaxDelayToWaitTimeUnit().toMillis(options.getMaxDelayToWait()); + trafficShapingHandler.setMaxWriteDelay(maxDelayToWaitInMillis); + } } } @@ -191,68 +231,53 @@ private synchronized Future listen(SocketAddress localAddress, ContextI } PromiseInternal promise = listenContext.promise(); if (main == null) { + + SSLHelper helper; + try { + helper = new SSLHelper(SSLHelper.resolveEngineOptions(options.getSslEngineOptions(), options.isUseAlpn())); + } catch (Exception e) { + return context.failedFuture(e); + } + // The first server binds the socket actualServer = this; bindFuture = promise; - sslHelper = createSSLHelper(); + sslHelper = helper; trafficShapingHandler = createTrafficShapingHandler(); childHandler = childHandler(listenContext, localAddress, trafficShapingHandler); - worker = ch -> childHandler.accept(ch, sslChannelProvider.result().sslChannelProvider()); + worker = ch -> { + Future scp = sslChannelProvider; + childHandler.accept(ch, scp != null ? scp.result() : null, sslHelper, options.getSslOptions()); + }; servers = new HashSet<>(); servers.add(this); channelBalancer = new ServerChannelLoadBalancer(vertx.getAcceptorEventLoopGroup().next()); + // + if (options.isSsl() && options.getKeyCertOptions() == null && options.getTrustOptions() == null) { + return context.failedFuture("Key/certificate is mandatory for SSL"); + } + // Register the server in the shared server list if (shared) { sharedNetServers.put(id, this); } - listenContext.addCloseHook(this); // Initialize SSL before binding - sslChannelProvider = sslHelper.updateSslContext(options.getSslOptions(), listenContext).onComplete(ar -> { - if (ar.succeeded()) { - - // Socket bind - channelBalancer.addWorker(eventLoop, worker); - ServerBootstrap bootstrap = new ServerBootstrap(); - bootstrap.group(vertx.getAcceptorEventLoopGroup(), channelBalancer.workers()); - if (options.isSsl()) { - bootstrap.childOption(ChannelOption.ALLOCATOR, PartialPooledByteBufAllocator.INSTANCE); + if (options.isSsl()) { + ServerSSLOptions sslOptions = options.getSslOptions(); + configure(sslOptions); + sslChannelProvider = sslHelper.resolveSslChannelProvider(sslOptions, null, sslOptions.isSni(), sslOptions.getClientAuth(), sslOptions.getApplicationLayerProtocols(), listenContext).onComplete(ar -> { + if (ar.succeeded()) { + bind(hostOrPath, context, bindAddress, localAddress, shared, promise, sharedNetServers, id); } else { - bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); + promise.fail(ar.cause()); } - - bootstrap.childHandler(channelBalancer); - applyConnectionOptions(localAddress.isDomainSocket(), bootstrap); - - // Actual bind - io.netty.util.concurrent.Future bindFuture = resolveAndBind(context, bindAddress, bootstrap); - bindFuture.addListener((GenericFutureListener>) res -> { - if (res.isSuccess()) { - Channel ch = res.getNow(); - log.trace("Net server listening on " + hostOrPath + ":" + ch.localAddress()); - if (shared) { - ch.closeFuture().addListener((ChannelFutureListener) channelFuture -> { - synchronized (sharedNetServers) { - sharedNetServers.remove(id); - } - }); - } - // Update port to actual port when it is not a domain socket as wildcard port 0 might have been used - if (bindAddress.isInetSocket()) { - actualPort = ((InetSocketAddress)ch.localAddress()).getPort(); - } - metrics = createMetrics(localAddress); - promise.complete(ch); - } else { - promise.fail(res.cause()); - } - }); - } else { - promise.fail(ar.cause()); - } - }); + }); + } else { + bind(hostOrPath, context, bindAddress, localAddress, shared, promise, sharedNetServers, id); + } bindFuture.onFailure(err -> { if (shared) { @@ -268,9 +293,11 @@ private synchronized Future listen(SocketAddress localAddress, ContextI // Server already exists with that host/port - we will use that actualServer = main; metrics = main.metrics; - sslChannelProvider = main.sslChannelProvider; childHandler = childHandler(listenContext, localAddress, main.trafficShapingHandler); - worker = ch -> childHandler.accept(ch, sslChannelProvider.result().sslChannelProvider()); + worker = ch -> { + Future scp = actualServer.sslChannelProvider; + childHandler.accept(ch, scp != null ? scp.result() : null, sslHelper, options.getSslOptions()); + }; actualServer.servers.add(this); actualServer.channelBalancer.addWorker(eventLoop, worker); listenContext.addCloseHook(this); @@ -280,6 +307,53 @@ private synchronized Future listen(SocketAddress localAddress, ContextI } } + private void bind( + String hostOrPath, + ContextInternal context, + SocketAddress bindAddress, + SocketAddress localAddress, + boolean shared, + Promise promise, + Map sharedNetServers, + ServerID id) { + // Socket bind + channelBalancer.addWorker(eventLoop, worker); + ServerBootstrap bootstrap = new ServerBootstrap(); + bootstrap.group(vertx.getAcceptorEventLoopGroup(), channelBalancer.workers()); + if (options.isSsl()) { + bootstrap.childOption(ChannelOption.ALLOCATOR, PartialPooledByteBufAllocator.INSTANCE); + } else { + bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); + } + + bootstrap.childHandler(channelBalancer); + applyConnectionOptions(localAddress.isDomainSocket(), bootstrap); + + // Actual bind + io.netty.util.concurrent.Future bindFuture = resolveAndBind(context, bindAddress, bootstrap); + bindFuture.addListener((GenericFutureListener>) res -> { + if (res.isSuccess()) { + Channel ch = res.getNow(); + log.trace("Net server listening on " + hostOrPath + ":" + ch.localAddress()); + if (shared) { + ch.closeFuture().addListener((ChannelFutureListener) channelFuture -> { + synchronized (sharedNetServers) { + sharedNetServers.remove(id); + } + }); + } + // Update port to actual port when it is not a domain socket as wildcard port 0 might have been used + if (bindAddress.isInetSocket()) { + actualPort = ((InetSocketAddress)ch.localAddress()).getPort(); + } + metrics = createMetrics(localAddress); + promise.complete(ch); + } else { + promise.fail(res.cause()); + } + }); + } + public boolean isListening() { return listening; } @@ -374,7 +448,7 @@ public static io.netty.util.concurrent.Future resolveAndBind(ContextInt if (impl.ipAddress() != null) { bind(bootstrap, impl.ipAddress(), socketAddress.port(), promise); } else { - AddressResolver resolver = vertx.addressResolver(); + HostnameResolver resolver = vertx.hostnameResolver(); io.netty.util.concurrent.Future fut = resolver.resolveHostname(context.nettyEventLoop(), socketAddress.host()); fut.addListener((GenericFutureListener>) future -> { if (future.isSuccess()) { diff --git a/src/main/java/io/vertx/core/net/impl/VertxHandler.java b/src/main/java/io/vertx/core/net/impl/VertxHandler.java index 2abe1e4e8e0..286affa4767 100644 --- a/src/main/java/io/vertx/core/net/impl/VertxHandler.java +++ b/src/main/java/io/vertx/core/net/impl/VertxHandler.java @@ -126,7 +126,7 @@ public C getConnection() { @Override public void channelWritabilityChanged(ChannelHandlerContext ctx) { C conn = getConnection(); - conn.handleInterestedOpsChanged(); + conn.channelWritabilityChanged(); } @Override diff --git a/src/main/java/io/vertx/core/net/impl/pool/Endpoint.java b/src/main/java/io/vertx/core/net/impl/endpoint/Endpoint.java similarity index 69% rename from src/main/java/io/vertx/core/net/impl/pool/Endpoint.java rename to src/main/java/io/vertx/core/net/impl/endpoint/Endpoint.java index 6d5023a2797..821930f35d9 100644 --- a/src/main/java/io/vertx/core/net/impl/pool/Endpoint.java +++ b/src/main/java/io/vertx/core/net/impl/endpoint/Endpoint.java @@ -8,18 +8,14 @@ * * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ -package io.vertx.core.net.impl.pool; - -import io.vertx.core.Future; -import io.vertx.core.Promise; -import io.vertx.core.impl.ContextInternal; +package io.vertx.core.net.impl.endpoint; /** * An endpoint, i.e a set of connection to the same address. * * @author Julien Viet */ -public abstract class Endpoint { +public abstract class Endpoint { private final Runnable dispose; private boolean closed; @@ -31,28 +27,28 @@ public Endpoint(Runnable dispose) { this.dispose = dispose; } - public Future getConnection(ContextInternal ctx, long timeout) { + boolean before() { synchronized (this) { if (disposed) { - return null; + return false; } pendingRequestCount++; } - return requestConnection(ctx, timeout).andThen(ar -> { - boolean dispose; - synchronized (Endpoint.this) { - pendingRequestCount--; - dispose = checkDispose(); - } - // Dispose before callback otherwise we can have the callback handler retrying the same - // endpoint and never get the callback it expects to creating an infinite loop - if (dispose) { - disposeInternal(); - } - }); + return true; } - public abstract Future requestConnection(ContextInternal ctx, long timeout); + void after() { + boolean dispose; + synchronized (Endpoint.this) { + pendingRequestCount--; + dispose = checkDispose(); + } + // Dispose before callback otherwise we can have the callback handler retrying the same + // endpoint and never get the callback it expects to creating an infinite loop + if (dispose) { + disposeInternal(); + } + } protected void checkExpired() { } @@ -97,7 +93,7 @@ protected void dispose() { } /** - * Close the endpoint, this will close all connections, this method is called by the {@link ConnectionManager} when + * Close the endpoint, this will close all connections, this method is called by the {@link EndpointManager} when * it is closed. */ protected void close() { diff --git a/src/main/java/io/vertx/core/net/impl/pool/ConnectionManager.java b/src/main/java/io/vertx/core/net/impl/endpoint/EndpointManager.java similarity index 54% rename from src/main/java/io/vertx/core/net/impl/pool/ConnectionManager.java rename to src/main/java/io/vertx/core/net/impl/endpoint/EndpointManager.java index 6ca2647d5e7..ccab055e2ed 100644 --- a/src/main/java/io/vertx/core/net/impl/pool/ConnectionManager.java +++ b/src/main/java/io/vertx/core/net/impl/endpoint/EndpointManager.java @@ -9,32 +9,30 @@ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ -package io.vertx.core.net.impl.pool; +package io.vertx.core.net.impl.endpoint; import io.vertx.core.Future; -import io.vertx.core.impl.ContextInternal; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; import java.util.function.Consumer; -import java.util.function.Function; /** - * The connection manager associates remote hosts with pools, it also tracks all connections so they can be closed - * when the manager is closed. + * The endpoint manager associates an arbitrary {@code } key with endpoints, it also tracks all endpoints, so they + * can be closed when the manager is closed. * * @author Tim Fox */ -public class ConnectionManager { +public class EndpointManager { - private static final Consumer> EXPIRED_CHECKER = Endpoint::checkExpired; + private static final Consumer EXPIRED_CHECKER = Endpoint::checkExpired; - private final EndpointProvider provider; - private final Map> endpointMap = new ConcurrentHashMap<>(); + private final Map endpointMap = new ConcurrentHashMap<>(); + private final AtomicInteger status = new AtomicInteger(); - public ConnectionManager(EndpointProvider provider) { - this.provider = provider; + public EndpointManager() { } /** @@ -49,12 +47,10 @@ public void checkExpired() { * * @param consumer the consumer to apply */ - public void forEach(Consumer> consumer) { + public void forEach(Consumer consumer) { endpointMap.values().forEach(consumer); } - private final AtomicInteger status = new AtomicInteger(); - /** * Resolve the couple {@code key} as an endpoint, the {@code function} is then applied on this endpoint and the value returned. * @@ -62,17 +58,20 @@ public void forEach(Consumer> consumer) { * @param function the function to apply on the endpoint * @return the value returned by the function when applied on the resolved endpoint. */ - public T withEndpoint(K key, Function, Optional> function) { + public T withEndpoint(K key, EndpointProvider provider, BiFunction function) { + checkStatus(); + Endpoint[] ref = new Endpoint[1]; while (true) { - Endpoint endpoint = endpointMap.computeIfAbsent(key, k -> { - Endpoint[] ref = new Endpoint[1]; - Endpoint ep = provider.create(key, () -> endpointMap.remove(key, ref[0])); + ref[0] = null; + E endpoint = endpointMap.computeIfAbsent(key, k -> { + E ep = provider.create(key, () -> endpointMap.remove(key, ref[0])); ref[0] = ep; return ep; }); - Optional opt = function.apply(endpoint); - if (opt.isPresent()) { - return opt.get(); + if (endpoint.before()) { + T value = function.apply(endpoint, endpoint == ref[0]); + endpoint.after(); + return value; } } } @@ -80,28 +79,36 @@ public T withEndpoint(K key, Function, Optional> function) { /** * Get a connection to an endpoint resolved by {@code key} * - * @param ctx the connection context * @param key the endpoint key * @return the future resolved with the connection */ - public Future getConnection(ContextInternal ctx, K key) { - return getConnection(ctx, key, 0); + public Future withEndpointAsync(K key, EndpointProvider provider, BiFunction> function) { + checkStatus(); + Endpoint[] ref = new Endpoint[1]; + while (true) { + ref[0] = null; + E endpoint = endpointMap.computeIfAbsent(key, k -> { + E ep = provider.create(key, () -> endpointMap.remove(key, ref[0])); + ref[0] = ep; + return ep; + }); + if (endpoint.before()) { + return function + .apply(endpoint, endpoint == ref[0]) + .andThen(ar -> { + endpoint.after(); + }); + } + } } - /** - * Like {@link #getConnection(ContextInternal, Object)} but with an acquisition timeout. - */ - public Future getConnection(ContextInternal ctx, K key, long timeout) { + private void checkStatus() { int st = status.get(); if (st == 1) { - return ctx.failedFuture("Pool shutdown"); + throw new IllegalStateException("Pool shutdown"); } else if (st == 2) { - return ctx.failedFuture("Pool closed"); + throw new IllegalStateException("Pool closed"); } - return withEndpoint(key, endpoint -> { - Future fut = endpoint.getConnection(ctx, timeout); - return Optional.ofNullable(fut); - }); } /** @@ -120,7 +127,7 @@ public void close() { if (val > 1) { break; } else if (status.compareAndSet(val, 2)) { - for (Endpoint endpoint : endpointMap.values()) { + for (Endpoint endpoint : endpointMap.values()) { endpoint.close(); } break; diff --git a/src/main/java/io/vertx/core/net/impl/pool/EndpointProvider.java b/src/main/java/io/vertx/core/net/impl/endpoint/EndpointProvider.java similarity index 72% rename from src/main/java/io/vertx/core/net/impl/pool/EndpointProvider.java rename to src/main/java/io/vertx/core/net/impl/endpoint/EndpointProvider.java index 0fb468ee37c..5e1c6788cb0 100644 --- a/src/main/java/io/vertx/core/net/impl/pool/EndpointProvider.java +++ b/src/main/java/io/vertx/core/net/impl/endpoint/EndpointProvider.java @@ -8,22 +8,22 @@ * * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ -package io.vertx.core.net.impl.pool; +package io.vertx.core.net.impl.endpoint; /** - * Provides endpoint to a {@link ConnectionManager}. + * Provides endpoint to a {@link EndpointManager}. * * @author Julien Viet */ -public interface EndpointProvider { +public interface EndpointProvider { /** - * Create an endpoint tracked by the {@link ConnectionManager}. + * Create an endpoint tracked by the {@link EndpointManager}. * * @param key the endpoint key * @param dispose the callback to signal this endpoint should be destroyed * @return the created endpoint */ - Endpoint create(K key, Runnable dispose); + E create(K key, Runnable dispose); } diff --git a/src/main/java/io/vertx/core/net/impl/pool/CombinerExecutor.java b/src/main/java/io/vertx/core/net/impl/pool/CombinerExecutor.java index b9083e2e108..052904d2eec 100644 --- a/src/main/java/io/vertx/core/net/impl/pool/CombinerExecutor.java +++ b/src/main/java/io/vertx/core/net/impl/pool/CombinerExecutor.java @@ -10,8 +10,11 @@ */ package io.vertx.core.net.impl.pool; +import io.netty.util.concurrent.FastThreadLocal; import io.netty.util.internal.PlatformDependent; +import java.util.HashMap; +import java.util.Map; import java.util.Queue; import java.util.concurrent.atomic.AtomicInteger; @@ -27,7 +30,20 @@ public class CombinerExecutor implements Executor { private final Queue> q = PlatformDependent.newMpscQueue(); private final AtomicInteger s = new AtomicInteger(); private final S state; - private final ThreadLocal current = new ThreadLocal<>(); + + protected static final class InProgressTail { + + final CombinerExecutor combiner; + Task task; + Map, Task> others; + + public InProgressTail(CombinerExecutor combiner, Task task) { + this.combiner = combiner; + this.task = task; + } + } + + private static final FastThreadLocal> current = new FastThreadLocal<>(); public CombinerExecutor(S state) { this.state = state; @@ -40,50 +56,69 @@ public void submit(Action action) { return; } Task head = null; + Task tail = null; do { try { - head = pollAndExecute(head); + for (; ; ) { + final Action a = q.poll(); + if (a == null) { + break; + } + final Task task = a.execute(state); + if (task != null) { + Task last = task.last(); + if (head == null) { + assert tail == null; + tail = last; + head = task; + } else { + tail.next(task); + tail = last; + } + } + } } finally { s.set(0); } } while (!q.isEmpty() && s.compareAndSet(0, 1)); if (head != null) { - Task inProgress = current.get(); + InProgressTail inProgress = (InProgressTail) current.get(); if (inProgress == null) { - current.set(head); + inProgress = new InProgressTail<>(this, tail); + current.set(inProgress); try { - while (head != null) { - head.run(); - head = head.next; - } + // from now one cannot trust tail anymore + head.runNextTasks(); + assert inProgress.others == null || inProgress.others.isEmpty(); } finally { current.remove(); } } else { - merge(inProgress, head); - } - } - } - - private Task pollAndExecute(Task head) { - Action action; - while ((action = q.poll()) != null) { - Task task = action.execute(state); - if (task != null) { - if (head == null) { - head = task; + if (inProgress.combiner == this) { + Task oldNextTail = inProgress.task.replaceNext(head); + assert oldNextTail == null; + inProgress.task = tail; } else { - merge(head, task); + Map, Task> map = inProgress.others; + if (map == null) { + map = inProgress.others = new HashMap<>(1); + } + Task task = map.get(this); + if (task == null) { + map.put(this, tail); + try { + // from now one cannot trust tail anymore + head.runNextTasks(); + } finally { + map.remove(this); + } + } else { + Task oldNextTail = task.replaceNext(head); + assert oldNextTail == null; + map.put(this, tail); + } } } } - return head; - } - - private static void merge(Task head, Task tail) { - Task tmp = tail.prev; - tail.prev = head.prev; - head.prev.next = tail; - head.prev = tmp; } } diff --git a/src/main/java/io/vertx/core/net/impl/pool/ConnectionPool.java b/src/main/java/io/vertx/core/net/impl/pool/ConnectionPool.java index 34bed1adc1b..ce388870c2c 100644 --- a/src/main/java/io/vertx/core/net/impl/pool/ConnectionPool.java +++ b/src/main/java/io/vertx/core/net/impl/pool/ConnectionPool.java @@ -14,7 +14,6 @@ import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.impl.ContextInternal; -import io.vertx.core.impl.EventLoopContext; import io.vertx.core.impl.VertxInternal; import java.util.List; @@ -31,10 +30,10 @@ public interface ConnectionPool { * This provider reuse the context argument when it can be cast or unwrapped to an event-loop context, otherwise * it returns a new event-loop context that reuses the Netty event-loop of the context argument. */ - Function EVENT_LOOP_CONTEXT_PROVIDER = ctx -> { + Function EVENT_LOOP_CONTEXT_PROVIDER = ctx -> { ctx = ctx.unwrap(); - if (ctx instanceof EventLoopContext) { - return (EventLoopContext)ctx; + if (ctx.isEventLoopContext()) { + return ctx; } else { VertxInternal vertx = ctx.owner(); return vertx.createEventLoopContext(ctx.nettyEventLoop(), ctx.workerPool(), ctx.classLoader()); @@ -72,7 +71,7 @@ static ConnectionPool pool(PoolConnector connector, int[] maxSizes, in * @param contextProvider the provider to be used by the pool * @return a reference to this, so the API can be used fluently */ - ConnectionPool contextProvider(Function contextProvider); + ConnectionPool contextProvider(Function contextProvider); /** * Acquire a connection from the pool. diff --git a/src/main/java/io/vertx/core/net/impl/pool/EndpointResolver.java b/src/main/java/io/vertx/core/net/impl/pool/EndpointResolver.java deleted file mode 100644 index eac1edc8051..00000000000 --- a/src/main/java/io/vertx/core/net/impl/pool/EndpointResolver.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ -package io.vertx.core.net.impl.pool; - -import io.vertx.core.Future; -import io.vertx.core.impl.ContextInternal; -import io.vertx.core.net.Address; -import io.vertx.core.net.SocketAddress; -import io.vertx.core.spi.resolver.AddressResolver; - -import java.util.Optional; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.BiFunction; -import java.util.function.Function; - -/** - * A {@link ConnectionManager} decorator that resolves a socket address from a name string. - * - * @param the resolver state type - * @param the resolution key type - * @param the connection type - * @param the resolved address type - */ -public class EndpointResolver extends ConnectionManager, C> { - - private final AddressResolver resolver; - - public EndpointResolver(EndpointProvider provider, AddressResolver endpointProvider, BiFunction resolver) { - super(new EndpointProvider<>() { - @Override - public Endpoint create(ResolvingKey key, Runnable dispose) { - ConnectionManager, C> connectionManager = new ConnectionManager<>((key_, dispose_) -> { - class Disposer implements Runnable { - @Override - public void run() { - key_.cleanup(); - dispose_.run(); - } - } - return provider.create(resolver.apply(key_.key, key_.address), new Disposer()); - }); - return new ResolvedEndpoint<>(endpointProvider.resolve(key.address), endpointProvider, dispose, connectionManager, key); - } - }); - - this.resolver = endpointProvider; - } - - /** - * Try to cast the {@code address} to the resolver address and then resolve the couple (address,key) as an - * endpoint, the {@code function} is then applied on the endpoint and the value returned. - * - * @param address the address to resolve - * @param key the endpoint key - * @param function the function to apply on the endpoint - * @return the value returned by the function when applied on the resolved endpoint. - */ - public T withEndpoint(Address address, K key, Function, Optional> function) { - A resolverAddress = resolver.tryCast(address); - if (resolverAddress == null) { - return null; - } else { - return withEndpoint(new ResolvingKey<>(key, resolverAddress), function); - } - } - - public static class ResolvedEndpoint extends Endpoint { - - private final AtomicReference state; - private final Future resolved; - private final AddressResolver resolver; - private final ConnectionManager, C> connectionManager; - private final ResolvingKey key; - - public ResolvedEndpoint(Future resolved, AddressResolver resolver, Runnable dispose, ConnectionManager, C> connectionManager, ResolvingKey key) { - super(() -> { - if (resolved.result() != null) { - resolver.dispose(resolved.result()); - } - dispose.run(); - }); - AtomicReference state = new AtomicReference<>(); - Future fut = resolved.andThen(ar -> { - if (ar.succeeded()) { - state.set(ar.result()); - } - }); - this.resolved = fut; - this.state = state; - this.resolver = resolver; - this.connectionManager = connectionManager; - this.key = key; - } - - public S state() { - return state.get(); - } - - public AddressResolver resolver() { - return resolver; - } - - @Override - public void checkExpired() { - connectionManager.checkExpired(); - } - - @Override - public Future requestConnection(ContextInternal ctx, long timeout) { - return resolved.compose(state -> resolver - .pickAddress(state) - .compose(origin -> { - incRefCount(); - return connectionManager.getConnection(ctx, new EndpointKey<>(origin, key.key) { - @Override - void cleanup() { - resolver.removeAddress(state, origin); - decRefCount(); - } - }, timeout); - })); - } - } - - private abstract static class EndpointKey { - final SocketAddress address; - public EndpointKey(SocketAddress address, K key) { - this.key = key; - this.address = address; - } - final K key; - abstract void cleanup(); - @Override - public int hashCode() { - return address.hashCode(); - } - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (obj instanceof EndpointResolver.EndpointKey) { - EndpointKey that = (EndpointKey) obj; - return address.equals(that.address); - } - return false; - } - @Override - public String toString() { - return "EndpointKey(address=" + address + ",key=" + key + ")"; - } - } -} diff --git a/src/main/java/io/vertx/core/net/impl/pool/PoolConnector.java b/src/main/java/io/vertx/core/net/impl/pool/PoolConnector.java index 882ebcc2bc5..d7768e12371 100644 --- a/src/main/java/io/vertx/core/net/impl/pool/PoolConnector.java +++ b/src/main/java/io/vertx/core/net/impl/pool/PoolConnector.java @@ -10,10 +10,8 @@ */ package io.vertx.core.net.impl.pool; -import io.vertx.core.AsyncResult; import io.vertx.core.Future; -import io.vertx.core.Handler; -import io.vertx.core.impl.EventLoopContext; +import io.vertx.core.impl.ContextInternal; /** * Defines the interactions with the actual back-end managing connections. @@ -29,7 +27,7 @@ public interface PoolConnector { * @param listener the listener * @return a future notified with the result */ - Future> connect(EventLoopContext context, Listener listener); + Future> connect(ContextInternal context, Listener listener); /** * Checks whether the connection is still valid. diff --git a/src/main/java/io/vertx/core/net/impl/pool/PoolWaiter.java b/src/main/java/io/vertx/core/net/impl/pool/PoolWaiter.java index 934ca290fc7..9563a5654c0 100644 --- a/src/main/java/io/vertx/core/net/impl/pool/PoolWaiter.java +++ b/src/main/java/io/vertx/core/net/impl/pool/PoolWaiter.java @@ -10,12 +10,8 @@ */ package io.vertx.core.net.impl.pool; -import io.vertx.core.AsyncResult; -import io.vertx.core.Context; -import io.vertx.core.Handler; import io.vertx.core.Promise; import io.vertx.core.impl.ContextInternal; -import io.vertx.core.impl.EventLoopContext; /** * A waiter for a connection. diff --git a/src/main/java/io/vertx/core/net/impl/pool/ResolvingKey.java b/src/main/java/io/vertx/core/net/impl/pool/ResolvingKey.java deleted file mode 100644 index f72e8e2765b..00000000000 --- a/src/main/java/io/vertx/core/net/impl/pool/ResolvingKey.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ -package io.vertx.core.net.impl.pool; - -import io.vertx.core.net.Address; - -import java.util.Objects; - -final class ResolvingKey { - - final K key; - final A address; - - ResolvingKey(K key, A address) { - this.key = Objects.requireNonNull(key); - this.address = Objects.requireNonNull(address); - } - - @Override - public int hashCode() { - return key.hashCode() ^ address.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj == this){ - return true; - } - if (obj instanceof ResolvingKey) { - ResolvingKey that = (ResolvingKey) obj; - return key.equals(that.key) && address.equals(that.address); - } - return false; - } - - @Override - public String toString() { - return "ResolvingKey(key=" + key + ",address=" + address + ")"; - } -} diff --git a/src/main/java/io/vertx/core/net/impl/pool/SemaphoreExecutor.java b/src/main/java/io/vertx/core/net/impl/pool/SemaphoreExecutor.java index 22adb16cd3c..b9defc2cdad 100644 --- a/src/main/java/io/vertx/core/net/impl/pool/SemaphoreExecutor.java +++ b/src/main/java/io/vertx/core/net/impl/pool/SemaphoreExecutor.java @@ -30,8 +30,9 @@ public void submit(Action action) { post = action.execute(state); } finally { lock.unlock(); - if (post != null) { + while (post != null) { post.run(); + post = post.next(); } } } diff --git a/src/main/java/io/vertx/core/net/impl/pool/SimpleConnectionPool.java b/src/main/java/io/vertx/core/net/impl/pool/SimpleConnectionPool.java index 480eced9dc1..91673628652 100644 --- a/src/main/java/io/vertx/core/net/impl/pool/SimpleConnectionPool.java +++ b/src/main/java/io/vertx/core/net/impl/pool/SimpleConnectionPool.java @@ -16,16 +16,13 @@ import io.vertx.core.Promise; import io.vertx.core.http.ConnectionPoolTooBusyException; import io.vertx.core.impl.ContextInternal; -import io.vertx.core.impl.EventLoopContext; import io.vertx.core.impl.future.Listener; -import io.vertx.core.impl.future.PromiseInternal; import java.util.AbstractList; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; @@ -118,7 +115,7 @@ public class SimpleConnectionPool implements ConnectionPool { static class Slot implements PoolConnector.Listener, PoolConnection { private final SimpleConnectionPool pool; - private final EventLoopContext context; + private final ContextInternal context; private final Promise result; private PoolWaiter initiator; private C connection; // The actual connection, might be null @@ -127,7 +124,7 @@ static class Slot implements PoolConnector.Listener, PoolConnection { private long concurrency; // The total number of times the connection can be acquired private int capacity; // The connection capacity - public Slot(SimpleConnectionPool pool, EventLoopContext context, int index, int capacity) { + public Slot(SimpleConnectionPool pool, ContextInternal context, int index, int capacity) { this.pool = pool; this.context = context; this.connection = null; @@ -185,7 +182,7 @@ public long concurrency() { // Selectors private BiFunction, List>, PoolConnection> selector; - private Function contextProvider; + private Function contextProvider; private BiFunction, List>, PoolConnection> fallbackSelector; // Connection state @@ -239,7 +236,7 @@ public ConnectionPool connectionSelector(BiFunction, List contextProvider(Function contextProvider) { + public ConnectionPool contextProvider(Function contextProvider) { this.contextProvider = contextProvider; return this; } @@ -372,10 +369,7 @@ public Task execute(SimpleConnectionPool pool) { } else { waiter.disposed = true; } - if (!pool.closed) { - pool.remove(removed); - } - return new Task() { + Task task = new Task() { @Override public void run() { if (waiter != null) { @@ -390,6 +384,14 @@ public void run() { removed.result.fail(cause); } }; + if (!pool.closed) { + Task removeTask = new Remove<>(removed).execute(pool); + if (removeTask != null) { + removeTask.next(task); + task = removeTask; + } + } + return task; } } @@ -413,7 +415,7 @@ public Task execute(SimpleConnectionPool pool) { removed.capacity = 0; PoolWaiter waiter = pool.waiters.poll(); if (waiter != null) { - EventLoopContext connectionContext = pool.contextProvider.apply(waiter.context); + ContextInternal connectionContext = pool.contextProvider.apply(waiter.context); Slot slot = new Slot<>(pool, connectionContext, removed.index, waiter.capacity); pool.capacity -= w; pool.capacity += waiter.capacity; @@ -526,15 +528,21 @@ public void run() { res.add(slot.connection); } } - for (Slot slot : removed) { - pool.remove(slot); - } - return new Task() { + Task head = new Task() { @Override public void run() { handler.handle(Future.succeededFuture(res)); } }; + Task tail = head; + for (Slot slot : removed) { + Task next = new Remove<>(slot).execute(pool); + if (next != null) { + tail.next(next); + tail = next; + } + } + return head; } } @@ -579,7 +587,7 @@ public void run() { // 2. Try create connection if (pool.capacity < pool.maxCapacity) { pool.capacity += capacity; - EventLoopContext connectionContext = pool.contextProvider.apply(context); + ContextInternal connectionContext = pool.contextProvider.apply(context); Slot slot2 = new Slot<>(pool, connectionContext, pool.size, capacity); pool.slots[pool.size++] = slot2; pool.requests++; diff --git a/src/main/java/io/vertx/core/net/impl/pool/Task.java b/src/main/java/io/vertx/core/net/impl/pool/Task.java index b5a58064ffe..7ced1366658 100644 --- a/src/main/java/io/vertx/core/net/impl/pool/Task.java +++ b/src/main/java/io/vertx/core/net/impl/pool/Task.java @@ -12,9 +12,41 @@ public abstract class Task { - Task prev = this; - Task next; + private Task next; - public abstract void run(); + public Task replaceNext(Task next) { + Task oldNext = this.next; + this.next = next; + return oldNext; + } + + public Task last() { + Task current = this; + Task next; + while ((next = current.next) != null) { + current = next; + } + return current; + } + + public Task next() { + return next; + } + public void next(Task next) { + this.next = next; + } + + protected final void runNextTasks() { + Task task = this; + while (task != null) { + task.run(); + final Task next = task.next; + // help GC :P + task.next = null; + task = next; + } + } + + public abstract void run(); } diff --git a/src/main/java/io/vertx/core/net/impl/resolver/EndpointResolverImpl.java b/src/main/java/io/vertx/core/net/impl/resolver/EndpointResolverImpl.java new file mode 100644 index 00000000000..bcd487772e3 --- /dev/null +++ b/src/main/java/io/vertx/core/net/impl/resolver/EndpointResolverImpl.java @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.net.impl.resolver; + +import io.vertx.core.Future; +import io.vertx.core.impl.ContextInternal; +import io.vertx.core.spi.loadbalancing.EndpointMetrics; +import io.vertx.core.loadbalancing.LoadBalancer; +import io.vertx.core.net.Address; +import io.vertx.core.net.SocketAddress; +import io.vertx.core.net.impl.endpoint.EndpointManager; +import io.vertx.core.net.impl.endpoint.Endpoint; +import io.vertx.core.net.impl.endpoint.EndpointProvider; +import io.vertx.core.spi.loadbalancing.EndpointSelector; +import io.vertx.core.spi.resolver.address.AddressResolver; +import io.vertx.core.spi.resolver.endpoint.EndpointLookup; +import io.vertx.core.spi.resolver.endpoint.EndpointRequest; +import io.vertx.core.spi.resolver.endpoint.EndpointResolver; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiFunction; + +/** + * A manager for endpoints. + * + * @author Julien Viet + */ +public class EndpointResolverImpl implements EndpointResolver { + + private final LoadBalancer loadBalancer; + private final AddressResolver> addressResolver; + private final EndpointManager connectionManager; + private final long expirationMillis; + + public EndpointResolverImpl(AddressResolver addressResolver, LoadBalancer loadBalancer, long expirationMillis) { + + if (loadBalancer == null) { + loadBalancer = LoadBalancer.ROUND_ROBIN; + } + + this.loadBalancer = loadBalancer; + this.addressResolver = (AddressResolver>) addressResolver; + this.connectionManager = new EndpointManager<>(); + this.expirationMillis = expirationMillis; + } + + /** + * @return whether the resolver accepts the {@code address} + */ + public A accepts(Address address) { + return addressResolver.tryCast(address); + } + + /** + * Trigger the expiration check, this removes unused entries. + */ + public void checkExpired() { + connectionManager.checkExpired(); + } + + private Future> resolve(A address) { + EndpointSelector selector = loadBalancer.selector(); + return addressResolver.resolve(selector::endpointOf, address) + .map(s -> new ManagedState<>(selector, s)); + } + + /** + * Select an endpoint. + * + * @param state the state + * @return the resolved endpoint + */ + private io.vertx.core.spi.loadbalancing.Endpoint selectEndpoint(ManagedState state) { + + List> lst = addressResolver.endpoints(state.state); + int idx = state.selector.selectEndpoint(lst); + if (idx >= 0 && idx < lst.size()) { + return lst.get(idx); + } + throw new UnsupportedOperationException("TODO"); + } + + /** + * Perform an endpoint lookup for the given {@code address} + * + * @param ctx the context + * @param address the address to lookup + * @return a future notified with the lookup + */ + public Future lookupEndpoint(ContextInternal ctx, Address address) { + return lookupEndpoint(ctx, address, 0); + } + + private Future lookupEndpoint(ContextInternal ctx, Address address, int attempts) { + A casted = addressResolver.tryCast(address); + if (casted == null) { + return ctx.failedFuture("Cannot resolve address " + address); + } + EndpointImpl ei = resolveAddress(ctx, casted, attempts > 0); + return ei.fut.compose(state -> { + if (!addressResolver.isValid(state.state)) { + // max 4 + if (attempts < 4) { + return lookupEndpoint(ctx, address, attempts + 1); + } else { + return ctx.failedFuture("Too many attempts"); + } + } + io.vertx.core.spi.loadbalancing.Endpoint endpoint = selectEndpoint(state); + return ctx.succeededFuture(new EndpointLookup() { + @Override + public SocketAddress address() { + return addressResolver.addressOfEndpoint(endpoint.endpoint()); + } + @Override + public EndpointRequest initiateRequest() { + ei.lastAccessed.set(System.currentTimeMillis()); + EndpointMetrics metrics = endpoint.metrics(); + Object metric = metrics.initiateRequest(); + return new EndpointRequest() { + @Override + public void reportRequestBegin() { + metrics.reportRequestBegin(metric); + } + @Override + public void reportRequestEnd() { + metrics.reportRequestEnd(metric); + } + @Override + public void reportResponseBegin() { + metrics.reportResponseBegin(metric); + } + @Override + public void reportResponseEnd() { + metrics.reportResponseEnd(metric); + } + @Override + public void reportFailure(Throwable failure) { + metrics.reportFailure(metric, failure); + } + }; + } + }); + }); + } + + private class EndpointImpl extends Endpoint { + + private volatile Future> fut; + private final AtomicLong lastAccessed; + private final AtomicBoolean disposed = new AtomicBoolean(); + + public EndpointImpl(Future> fut, Runnable dispose) { + super(dispose); + this.fut = fut; + this.lastAccessed = new AtomicLong(System.currentTimeMillis()); + } + + @Override + protected void dispose() { + if (fut.succeeded()) { + addressResolver.dispose(fut.result().state); + } + } + + @Override + protected void checkExpired() { +// Future> f = fut; + if (/*(f.succeeded() && !addressResolver.isValid(f.result().state)) ||*/ expirationMillis > 0 && System.currentTimeMillis() - lastAccessed.get() >= expirationMillis) { + if (disposed.compareAndSet(false, true)) { + decRefCount(); + } + } + } + + @Override + public boolean incRefCount() { + return super.incRefCount(); + } + + @Override + public boolean decRefCount() { + return super.decRefCount(); + } + } + + /** + * Internal structure. + */ + private class Result { + final Future> fut; + final EndpointImpl endpoint; + final boolean created; + public Result(Future> fut, EndpointImpl endpoint, boolean created) { + this.fut = fut; + this.endpoint = endpoint; + this.created = created; + } + } + + private EndpointImpl resolveAddress(ContextInternal ctx, A address, boolean refresh) { + EndpointProvider provider = (key, dispose) -> { + Future> fut = resolve(key); + EndpointImpl endpoint = new EndpointImpl(fut, dispose); + endpoint.incRefCount(); + return endpoint; + }; + BiFunction fn = (endpoint, created) -> { + if (refresh) { + endpoint.fut = resolve(address); + } + return new Result(endpoint.fut, endpoint, created); + }; + Result sFuture = connectionManager.withEndpoint(address, provider, fn); + if (sFuture.created) { + sFuture.fut.onFailure(err -> { + if (sFuture.endpoint.disposed.compareAndSet(false, true)) { + // We need to call decRefCount outside the withEndpoint method, hence we need + // the Result class workaround + sFuture.endpoint.decRefCount(); + } + }); + } + return sFuture.endpoint; + } +} diff --git a/src/main/java/io/vertx/core/net/impl/resolver/ManagedState.java b/src/main/java/io/vertx/core/net/impl/resolver/ManagedState.java new file mode 100644 index 00000000000..f0f4046a94f --- /dev/null +++ b/src/main/java/io/vertx/core/net/impl/resolver/ManagedState.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.net.impl.resolver; + +import io.vertx.core.spi.loadbalancing.EndpointSelector; + +import java.util.Objects; + +/** + */ +final class ManagedState { + + final S state; + final EndpointSelector selector; + + ManagedState(EndpointSelector selector, S state) { + this.selector = selector; + this.state = Objects.requireNonNull(state); + } +} diff --git a/src/main/java/io/vertx/core/parsetools/impl/RecordParserImpl.java b/src/main/java/io/vertx/core/parsetools/impl/RecordParserImpl.java index 5ada3d52643..eb33c58b798 100644 --- a/src/main/java/io/vertx/core/parsetools/impl/RecordParserImpl.java +++ b/src/main/java/io/vertx/core/parsetools/impl/RecordParserImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -14,10 +14,12 @@ import io.netty.buffer.Unpooled; import io.vertx.core.Handler; import io.vertx.core.buffer.Buffer; +import io.vertx.core.buffer.impl.BufferInternal; import io.vertx.core.impl.Arguments; import io.vertx.core.parsetools.RecordParser; import io.vertx.core.streams.ReadStream; +import java.nio.charset.StandardCharsets; import java.util.Objects; /** @@ -27,7 +29,7 @@ public class RecordParserImpl implements RecordParser { // Empty and unmodifiable - private static final Buffer EMPTY_BUFFER = Buffer.buffer(Unpooled.EMPTY_BUFFER); + private static final Buffer EMPTY_BUFFER = BufferInternal.buffer(Unpooled.EMPTY_BUFFER); private Buffer buff = EMPTY_BUFFER; private int pos; // Current position in buffer @@ -51,6 +53,7 @@ private RecordParserImpl(ReadStream stream) { this.stream = stream; } + @Override public void setOutput(Handler output) { Objects.requireNonNull(output, "output"); eventHandler = output; @@ -64,12 +67,7 @@ public void setOutput(Handler output) { * @return The byte[] form of the string */ public static Buffer latin1StringToBytes(String str) { - byte[] bytes = new byte[str.length()]; - for (int i = 0; i < str.length(); i++) { - char c = str.charAt(i); - bytes[i] = (byte) (c & 0xFF); - } - return Buffer.buffer(bytes); + return Buffer.buffer(str.getBytes(StandardCharsets.ISO_8859_1)); } /** @@ -126,6 +124,7 @@ public static RecordParser newFixed(int size, ReadStream stream, Handler * * @param delim the new delimeter */ + @Override public void delimitedMode(String delim) { delimitedMode(latin1StringToBytes(delim)); } @@ -138,6 +137,7 @@ public void delimitedMode(String delim) { * * @param delim the new delimiter */ + @Override public void delimitedMode(Buffer delim) { Objects.requireNonNull(delim, "delim"); delimited = true; @@ -152,6 +152,7 @@ public void delimitedMode(Buffer delim) { * * @param size the new record size */ + @Override public void fixedSizeMode(int size) { Arguments.require(size > 0, "Size must be > 0"); delimited = false; @@ -167,6 +168,7 @@ public void fixedSizeMode(int size) { * @param size the maximum record size * @return a reference to this, so the API can be used fluently */ + @Override public RecordParser maxRecordSize(int size) { Arguments.require(size > 0, "Size must be > 0"); maxRecordSize = size; @@ -276,11 +278,19 @@ private int parseFixed() { * * @param buffer a chunk of data */ + @Override public void handle(Buffer buffer) { - if (buff.length() == 0) { - buff = buffer; - } else { - buff.appendBuffer(buffer); + if (buffer.length() != 0) { + if (buff == EMPTY_BUFFER) { + // Copy the initial buffer instead of growing it. + // We cannot assume that we can modify the input, + // or that the buffer has enough capacity. + // For example, an HTTP client response sent over an encrypted connection + // emits un-pooled buffers with limited capacity. + buff = buffer.getBuffer(0, buffer.length()); + } else { + buff.appendBuffer(buffer); + } } handleParsing(); if (buff != null && maxRecordSize > 0 && buff.length() > maxRecordSize) { diff --git a/src/main/java/io/vertx/core/shareddata/SharedData.java b/src/main/java/io/vertx/core/shareddata/SharedData.java index 313c2b32553..cbaa73fa911 100644 --- a/src/main/java/io/vertx/core/shareddata/SharedData.java +++ b/src/main/java/io/vertx/core/shareddata/SharedData.java @@ -13,6 +13,11 @@ import io.vertx.codegen.annotations.VertxGen; import io.vertx.core.Future; +import io.vertx.core.VertxException; +import io.vertx.core.shareddata.impl.SharedDataImpl; + +import java.util.function.Function; +import java.util.function.Supplier; /** * Shared data allows you to share data safely between different parts of your application in a safe way. @@ -71,7 +76,7 @@ public interface SharedData { Future> getLocalAsyncMap(String name); /** - * Get an asynchronous lock with the specified name. The lock will be passed to the handler when it is available. + * Get an asynchronous lock with the specified name. The returned future will be completed with the lock when it is available. *

* In general lock acquision is unordered, so that sequential attempts to acquire a lock, * even from a single thread, can happen in non-sequential order. @@ -83,8 +88,8 @@ public interface SharedData { Future getLock(String name); /** - * Like {@link #getLock(String)} but specifying a timeout. If the lock is not obtained within the timeout - * a failure will be sent to the handler. + * Like {@link #getLock(String)} but specifying a timeout. If the lock is not obtained within the timeout the returned + * future is failed. *

* In general lock acquision is unordered, so that sequential attempts to acquire a lock, * even from a single thread, can happen in non-sequential order. @@ -97,7 +102,50 @@ public interface SharedData { Future getLockWithTimeout(String name, long timeout); /** - * Get an asynchronous local lock with the specified name. The lock will be passed to the handler when it is available. + * Get an asynchronous lock with the specified name. + * + *

When the {@code block} is called, the lock is already acquired, it will be released when the + * {@code Future} returned by the block completes. + * + *

When the {@code block} fails, the lock is released and the returned future is failed with the cause of the failure. + * + *

In general lock acquision is unordered, so that sequential attempts to acquire a lock, even from a single thread, + * can happen in non-sequential order. + * + * @param name the name of the lock + * @return the future returned by the {@code block} + */ + default Future withLock(String name, Supplier> block) { + return withLock(name, SharedDataImpl.DEFAULT_LOCK_TIMEOUT, block); + } + + /** + * Like {@link #withLock(String, Supplier)} but specifying a timeout. If the lock is not obtained within the timeout + * the returned future is failed. + * + * @param name the name of the lock + * @param timeout the timeout in ms + * @param block the code block called after lock acquisition + * @return the future returned by the {@code block} + */ + default Future withLock(String name, long timeout, Supplier> block) { + return getLockWithTimeout(name, timeout) + .compose(lock -> { + Future res; + try { + res = block.get(); + } catch (Exception e) { + lock.release(); + throw new VertxException(e); + } + return res.andThen(ar -> { + lock.release(); + }); + }); + } + + /** + * Get an asynchronous local lock with the specified name. The returned future will be completed with the lock when it is available. *

* In general lock acquision is unordered, so that sequential attempts to acquire a lock, * even from a single thread, can happen in non-sequential order. @@ -109,8 +157,8 @@ public interface SharedData { Future getLocalLock(String name); /** - * Like {@link #getLocalLock(String)} but specifying a timeout. If the lock is not obtained within the timeout - * a failure will be sent to the handler. + * Like {@link #getLocalLock(String)} but specifying a timeout. If the lock is not obtained within the timeout the returned + * future is failed. *

* In general lock acquision is unordered, so that sequential attempts to acquire a lock, * even from a single thread, can happen in non-sequential order. @@ -122,6 +170,49 @@ public interface SharedData { */ Future getLocalLockWithTimeout(String name, long timeout); + /** + * Get an asynchronous local lock with the specified name. + * + *

When the {@code block} is called, the lock is already acquired, it will be released when the + * {@code Future} returned by the block completes. + * + *

When the {@code block} fails, the lock is released and the returned future is failed with the cause of the failure. + * + *

In general lock acquision is unordered, so that sequential attempts to acquire a lock, even from a single thread, + * can happen in non-sequential order. + * + * @param name the name of the lock + * @return the future returned by the {@code block} + */ + default Future withLocalLock(String name, Supplier> block) { + return withLocalLock(name, SharedDataImpl.DEFAULT_LOCK_TIMEOUT, block); + } + + /** + * Like {@link #withLocalLock(String, Supplier)} but specifying a timeout. If the lock is not obtained within the timeout + * the returned future is failed. + * + * @param name the name of the lock + * @param timeout the timeout in ms + * @param block the code block called after lock acquisition + * @return the future returned by the {@code block} + */ + default Future withLocalLock(String name, long timeout, Supplier> block) { + return getLocalLockWithTimeout(name, timeout) + .compose(lock -> { + Future res; + try { + res = block.get(); + } catch (Exception e) { + lock.release(); + throw new VertxException(e); + } + return res.andThen(ar -> { + lock.release(); + }); + }); + } + /** * Get an asynchronous counter. The counter will be passed to the handler. * diff --git a/src/main/java/io/vertx/core/shareddata/impl/SharedDataImpl.java b/src/main/java/io/vertx/core/shareddata/impl/SharedDataImpl.java index f39b01db5d3..6c2eaa45b3f 100644 --- a/src/main/java/io/vertx/core/shareddata/impl/SharedDataImpl.java +++ b/src/main/java/io/vertx/core/shareddata/impl/SharedDataImpl.java @@ -34,7 +34,7 @@ */ public class SharedDataImpl implements SharedData { - private static final long DEFAULT_LOCK_TIMEOUT = 10 * 1000; + public static final long DEFAULT_LOCK_TIMEOUT = 10 * 1000; private final VertxInternal vertx; private final ClusterManager clusterManager; diff --git a/src/main/java/io/vertx/core/spi/VertxMetricsFactory.java b/src/main/java/io/vertx/core/spi/VertxMetricsFactory.java index d1d989e0076..1e2ac6457fb 100644 --- a/src/main/java/io/vertx/core/spi/VertxMetricsFactory.java +++ b/src/main/java/io/vertx/core/spi/VertxMetricsFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -13,14 +13,10 @@ import io.vertx.core.VertxOptions; import io.vertx.core.impl.VertxBuilder; -import io.vertx.core.impl.launcher.commands.BareCommand; import io.vertx.core.json.JsonObject; import io.vertx.core.metrics.MetricsOptions; -import io.vertx.core.metrics.impl.DummyVertxMetrics; import io.vertx.core.spi.metrics.VertxMetrics; -import static io.vertx.core.impl.launcher.commands.BareCommand.METRICS_OPTIONS_PROP_PREFIX; - /** * A factory for the plugable metrics SPI. * @@ -31,24 +27,8 @@ public interface VertxMetricsFactory extends VertxServiceProvider { @Override default void init(VertxBuilder builder) { if (builder.metrics() == null) { - JsonObject config = builder.config(); - MetricsOptions metricsOptions; - VertxOptions options = builder.options(); - if (config != null && config.containsKey("metricsOptions")) { - metricsOptions = newOptions(config.getJsonObject("metricsOptions")); - } else { - metricsOptions = options.getMetricsOptions(); - if (metricsOptions == null) { - metricsOptions = newOptions(); - } else { - metricsOptions = newOptions(metricsOptions); - } - } - BareCommand.configureFromSystemProperties(metricsOptions, METRICS_OPTIONS_PROP_PREFIX);; - builder.options().setMetricsOptions(metricsOptions); - if (options.getMetricsOptions().isEnabled()) { - builder.metrics(metrics(options)); - } + VertxOptions vertxOptions = builder.options(); + builder.metrics(metrics(vertxOptions)); } } @@ -65,7 +45,6 @@ default void init(VertxBuilder builder) { /** * Create an empty metrics options. * Providers can override this method to provide a custom metrics options subclass that exposes custom configuration. - * It is used by the {@link io.vertx.core.Launcher} class when creating new options when building a CLI Vert.x. * * @implSpec The default implementation returns {@link MetricsOptions#MetricsOptions()} * @return new metrics options @@ -90,7 +69,6 @@ default MetricsOptions newOptions(MetricsOptions options) { /** * Create metrics options from the provided {@code jsonObject}. *

Providers can override this method to provide a custom metrics options subclass that exposes custom configuration. - *

It is used by the {@link io.vertx.core.Launcher} class when creating new options when building a CLI Vert.x. * * @implSpec The default implementation calls {@link MetricsOptions#MetricsOptions(JsonObject)} )} with {@code jsonObject} * @param jsonObject json provided by the user diff --git a/src/main/java/io/vertx/core/spi/VertxTracerFactory.java b/src/main/java/io/vertx/core/spi/VertxTracerFactory.java index 65ff7134a4f..d7c9be6e312 100644 --- a/src/main/java/io/vertx/core/spi/VertxTracerFactory.java +++ b/src/main/java/io/vertx/core/spi/VertxTracerFactory.java @@ -24,16 +24,19 @@ public interface VertxTracerFactory extends VertxServiceProvider { /** - * Noop tracer factory, it can be useful for disabling metrics, e.g + * Noop tracer factory, it can be useful for disabling metrics, e.g. * {@code new VertxOptions().setTracingOptions(new TracingOptions().setFactory(VertxTracerFactory.NOOP))} */ VertxTracerFactory NOOP = options -> VertxTracer.NOOP; @Override default void init(VertxBuilder builder) { - TracingOptions options = builder.options().getTracingOptions(); - if (options != null && builder.tracer() == null) { - builder.tracer(tracer(options)); + if (builder.tracer() == null) { + TracingOptions tracingOptions = builder.options().getTracingOptions(); + if (tracingOptions == null) { + tracingOptions = newOptions(); + } + builder.tracer(tracer(tracingOptions)); } } @@ -50,7 +53,6 @@ default void init(VertxBuilder builder) { /** * Create an empty tracing options. * Providers can override this method to provide a custom tracing options subclass that exposes custom configuration. - * It is used by the {@link io.vertx.core.Launcher} class when creating new options when building a CLI Vert.x. * * @return new tracing options */ @@ -61,7 +63,6 @@ default TracingOptions newOptions() { /** * Create tracing options from the provided {@code jsonObject}. * Providers can override this method to provide a custom tracing options subclass that exposes custom configuration. - * It is used by the {@link io.vertx.core.Launcher} class when creating new options when building a CLI Vert.x. * * @param jsonObject json provided by the user * @return new tracing options diff --git a/src/main/java/io/vertx/core/spi/cluster/NodeSelector.java b/src/main/java/io/vertx/core/spi/cluster/NodeSelector.java index 062322c24cf..f767a029a6e 100644 --- a/src/main/java/io/vertx/core/spi/cluster/NodeSelector.java +++ b/src/main/java/io/vertx/core/spi/cluster/NodeSelector.java @@ -13,7 +13,6 @@ import io.vertx.core.Promise; import io.vertx.core.Vertx; -import io.vertx.core.eventbus.Message; import io.vertx.core.impl.VertxBuilder; import io.vertx.core.spi.VertxServiceProvider; @@ -47,20 +46,16 @@ default void init(VertxBuilder builder) { * *

The provided {@code promise} needs to be completed with {@link Promise#tryComplete} and {@link Promise#tryFail} * as it might completed outside the selector. - * - * @throws IllegalArgumentException if {@link Message#isSend()} returns {@code false} */ - void selectForSend(Message message, Promise promise); + void selectForSend(String address, Promise promise); /** * Select a node for publishing the given {@code message}. * *

The provided {@code promise} needs to be completed with {@link Promise#tryComplete} and {@link Promise#tryFail} * as it might completed outside the selector. - * - * @throws IllegalArgumentException if {@link Message#isSend()} returns {@code true} */ - void selectForPublish(Message message, Promise> promise); + void selectForPublish(String address, Promise> promise); /** * Invoked by the {@link ClusterManager} when messaging handler registrations are added or removed. diff --git a/src/main/java/io/vertx/core/spi/cluster/impl/DefaultNodeSelector.java b/src/main/java/io/vertx/core/spi/cluster/impl/DefaultNodeSelector.java index 6198477d340..72007135316 100644 --- a/src/main/java/io/vertx/core/spi/cluster/impl/DefaultNodeSelector.java +++ b/src/main/java/io/vertx/core/spi/cluster/impl/DefaultNodeSelector.java @@ -13,8 +13,6 @@ import io.vertx.core.Promise; import io.vertx.core.Vertx; -import io.vertx.core.eventbus.Message; -import io.vertx.core.impl.Arguments; import io.vertx.core.spi.cluster.ClusterManager; import io.vertx.core.spi.cluster.NodeSelector; import io.vertx.core.spi.cluster.RegistrationUpdateEvent; @@ -37,17 +35,15 @@ public void eventBusStarted() { } @Override - public void selectForSend(Message message, Promise promise) { - Arguments.require(message.isSend(), "selectForSend used for publishing"); - selectors.withSelector(message, promise, (prom, selector) -> { + public void selectForSend(String address, Promise promise) { + selectors.withSelector(address, promise, (prom, selector) -> { prom.tryComplete(selector.selectForSend()); }); } @Override - public void selectForPublish(Message message, Promise> promise) { - Arguments.require(!message.isSend(), "selectForPublish used for sending"); - selectors.withSelector(message, promise, (prom, selector) -> { + public void selectForPublish(String address, Promise> promise) { + selectors.withSelector(address, promise, (prom, selector) -> { prom.tryComplete(selector.selectForPublish()); }); } diff --git a/src/main/java/io/vertx/core/spi/cluster/impl/selector/Selectors.java b/src/main/java/io/vertx/core/spi/cluster/impl/selector/Selectors.java index 2244e85cc6f..0e5423e0153 100644 --- a/src/main/java/io/vertx/core/spi/cluster/impl/selector/Selectors.java +++ b/src/main/java/io/vertx/core/spi/cluster/impl/selector/Selectors.java @@ -35,8 +35,7 @@ public Selectors(ClusterManager clusterManager) { this.clusterManager = clusterManager; } - public void withSelector(Message message, Promise promise, BiConsumer, RoundRobinSelector> task) { - String address = message.address(); + public void withSelector(String address, Promise promise, BiConsumer, RoundRobinSelector> task) { SelectorEntry entry = map.compute(address, (addr, curr) -> { return curr == null ? new SelectorEntry() : (curr.isNotReady() ? curr.increment() : curr); }); diff --git a/src/main/java/io/vertx/core/spi/launcher/Command.java b/src/main/java/io/vertx/core/spi/launcher/Command.java deleted file mode 100644 index f2cdbc10407..00000000000 --- a/src/main/java/io/vertx/core/spi/launcher/Command.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.spi.launcher; - -import io.vertx.core.cli.CLIException; -import io.vertx.core.cli.annotations.Argument; -import io.vertx.core.cli.annotations.Description; -import io.vertx.core.cli.annotations.Option; -import io.vertx.core.cli.annotations.Summary; - -/** - * A plug-in to the Vert.x command or {@link io.vertx.core.Launcher} class. Each command instance is created - * by a {@link CommandFactory}. - *

- * {@link Command} implementation can retrieve argument and option using the {@link Argument} and {@link - * Option} annotations. Documentation / help is provided using the {@link Summary} (single sentence) and - * {@link Description} annotations. - *

- * Commands follow a strict lifecycle. The {@link #setUp(ExecutionContext)} method is called with an - * execution context. It lets you validate the inputs and prepare the environment is needed. The - * {@link #run()} method is called immediately after {@link #setUp(ExecutionContext)}, and executes the - * command. Finally, once the command has completed, the {@link #tearDown()} method is called. In this method - * you have the opportunity to cleanup. - *

- * - * @author Clement Escoffier - */ -public interface Command { - - /** - * Set up the command execution environment. - * The command line model has been retrieved and is frozen. Values has been set / injected. You can use - * this callback to validate the inputs. - * - * @param context the context - * @throws CLIException if the validation failed - */ - void setUp(ExecutionContext context) throws CLIException; - - /** - * Executes the command. - * - * @throws CLIException If anything went wrong. - */ - void run() throws CLIException; - - /** - * The command has been executed. Use this method to cleanup the environment. - * - * @throws CLIException if anything went wrong - */ - void tearDown() throws CLIException; - -} diff --git a/src/main/java/io/vertx/core/spi/launcher/CommandFactory.java b/src/main/java/io/vertx/core/spi/launcher/CommandFactory.java deleted file mode 100644 index 738a00e5e14..00000000000 --- a/src/main/java/io/vertx/core/spi/launcher/CommandFactory.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.spi.launcher; - -import io.vertx.core.Launcher; -import io.vertx.core.cli.CLI; -import io.vertx.core.cli.CommandLine; - -/** - * SPI Interface to provide a new {@link Launcher} command. Implementors needs to provide two methods: - *
    - *
  1. {@link #define()} - creates a {@link CLI} instance (so the model)
  2. - *
  3. {@link #create(CommandLine)}} - creates a new command instance
  4. - *
- * - * @author Clement Escoffier - */ -public interface CommandFactory { - - /** - * @return a new instance of the command. - */ - C create(CommandLine evaluated); - - /** - * Creates a new {@link CLI} instance. - * - * @return the CLI. - */ - CLI define(); - - -} diff --git a/src/main/java/io/vertx/core/spi/launcher/CommandFactoryLookup.java b/src/main/java/io/vertx/core/spi/launcher/CommandFactoryLookup.java deleted file mode 100644 index b51f6c46993..00000000000 --- a/src/main/java/io/vertx/core/spi/launcher/CommandFactoryLookup.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.spi.launcher; - -import io.vertx.core.impl.launcher.ServiceCommandFactoryLoader; - -import java.util.Collection; - -/** - * The interface to implement to look for commands. - * - * @see ServiceCommandFactoryLoader - * @author Clement Escoffier - */ -public interface CommandFactoryLookup { - - /** - * Looks for command implementation and instantiated them. - * - * @return the set of commands, empty if none are found. - */ - Collection> lookup(); - -} diff --git a/src/main/java/io/vertx/core/spi/launcher/DefaultCommand.java b/src/main/java/io/vertx/core/spi/launcher/DefaultCommand.java deleted file mode 100644 index fd9d5d20423..00000000000 --- a/src/main/java/io/vertx/core/spi/launcher/DefaultCommand.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.spi.launcher; - - -import io.vertx.core.cli.CLIException; -import io.vertx.core.cli.annotations.Description; -import io.vertx.core.cli.annotations.Hidden; -import io.vertx.core.cli.annotations.Option; - -import java.io.File; -import java.io.PrintStream; -import java.util.List; - -/** - * Default implementation of {@link Command} using annotation to define itself. It is highly recommended - * to extend this class when implementing a command. - *

- * + *

* No specific thread and context can be expected when this method is called. * * @param address the address used to register the handler - * @param repliedAddress null when the handler is not a reply handler, otherwise the address this handler is replying to */ - default H handlerRegistered(String address, String repliedAddress) { + default H handlerRegistered(String address) { return null; } diff --git a/src/main/java/io/vertx/core/spi/resolver/AddressResolver.java b/src/main/java/io/vertx/core/spi/resolver/AddressResolver.java deleted file mode 100644 index ff346c15113..00000000000 --- a/src/main/java/io/vertx/core/spi/resolver/AddressResolver.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ -package io.vertx.core.spi.resolver; - -import io.vertx.core.Future; -import io.vertx.core.net.Address; -import io.vertx.core.net.SocketAddress; - -/** - * Name resolver Service Provider Interface (SPI). - * - *

{@link #resolve(Address)} resolves an address to resolver managed state {@code }. Such state can be queried - * and mutated by the resolver, e.g. {@link #pickAddress(Object)} chooses an {@code SocketAddress} address and might - * update the provided state. State modifying methods can be called concurrently, the implementation is responsible - * to manage the concurrent state modifications. - * - * @param the type of the state managed by the resolver - * @param the type of {@link Address} resolved - * @param the type of metrics, implementations can use {@code Void} when metrics are not managed - */ -public interface AddressResolver { - - /** - * Try to cast the {@code address} to an address instance that can be resolved by this resolver instance. - * - * @param address the address to cast - * @return the address or {@code null} when the {@code address} cannot be resolved by this resolver - */ - A tryCast(Address address); - - /** - * Resolve an address to the resolver state for this name. - * - * @param address the address to resolve - * @return a future notified with the result - */ - Future resolve(A address); - - /** - * Pick a socket address for the state. - * - * @param state the state - * @return the resolved socket address - */ - Future pickAddress(S state); - - /** - * Remove a stale address from the state. - * - * @param state the state to update - * @param address the stale address - */ - void removeAddress(S state, SocketAddress address); - - /** - * Dispose the state. - * - * @param state the state - */ - void dispose(S state); - - /** - * Signal the beginning of a request operated by the client - * - * @param state the state - * @param address the resolved address of the request - * @return the request/response metric - */ - default M requestBegin(S state, SocketAddress address) { - return null; - } - - /** - * Signal the end of the request attached to the {@code metric} - * @param metric the request/response metric - */ - default void requestEnd(M metric) {} - - /** - * Signal the beginning of the response attached to the {@code metric} - * @param metric the request/response metric - */ - default void responseBegin(M metric) {} - - /** - * Signal the end of the response attached to the {@code metric} - * @param metric the request metric - */ - default void responseEnd(M metric) {} -} diff --git a/src/main/java/io/vertx/core/spi/resolver/address/AddressResolver.java b/src/main/java/io/vertx/core/spi/resolver/address/AddressResolver.java new file mode 100644 index 00000000000..789a8e12b9a --- /dev/null +++ b/src/main/java/io/vertx/core/spi/resolver/address/AddressResolver.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.spi.resolver.address; + +import io.vertx.core.Future; +import io.vertx.core.net.Address; +import io.vertx.core.net.SocketAddress; + +import java.util.List; +import java.util.function.Function; + +/** + * Address resolver Service Provider Interface (SPI). + * + *

{@link #resolve)} resolves an address to resolver managed state {@code }. State modifying methods can be called concurrently, the implementation is responsible + * to manage the concurrent state modifications. + * + * @param the type of the state managed by the resolver + * @param the type of {@link Address} resolved + * @param the type of the endpoint + * @param the type of the wrapped endpoint + */ +public interface AddressResolver { + + /** + * Try to cast the {@code address} to an address instance that can be resolved by this resolver instance. + * + * @param address the address to cast + * @return the address or {@code null} when the {@code address} cannot be resolved by this resolver + */ + A tryCast(Address address); + + /** + * Returns the socket address of a given {@code endpoint}. + * + * @param endpoint the endpoint + * @return the endpoint socket address + */ + SocketAddress addressOfEndpoint(E endpoint); + + /** + * Resolve an address to the resolver state for this name. + * + * @param factory the endpoint factory + * @param address the address to resolve + * @return a future notified with the result + */ + Future resolve(Function factory, A address); + + /** + * Return the current list of endpoint visible by the resolver. + * + * @param state the resolver state + * @return the list of endpoints + */ + List endpoints(S state); + + /** + * Check the state validity. + * + * @param state resolver state + * @return the state validity + */ + boolean isValid(S state); + + /** + * Dispose the state. + * + * @param state the state + */ + void dispose(S state); + + /** + * Close this resolver. + */ + void close(); + +} diff --git a/src/main/java/io/vertx/core/spi/resolver/ResolverProvider.java b/src/main/java/io/vertx/core/spi/resolver/dns/AddressResolverProvider.java similarity index 72% rename from src/main/java/io/vertx/core/spi/resolver/ResolverProvider.java rename to src/main/java/io/vertx/core/spi/resolver/dns/AddressResolverProvider.java index 85fbfd9f88d..0a99b3befd7 100644 --- a/src/main/java/io/vertx/core/spi/resolver/ResolverProvider.java +++ b/src/main/java/io/vertx/core/spi/resolver/dns/AddressResolverProvider.java @@ -9,17 +9,16 @@ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 */ -package io.vertx.core.spi.resolver; +package io.vertx.core.spi.resolver.dns; import io.netty.resolver.AddressResolverGroup; import io.vertx.core.Future; -import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.VertxException; import io.vertx.core.dns.AddressResolverOptions; -import io.vertx.core.impl.VertxImpl; -import io.vertx.core.impl.resolver.DnsResolverProvider; -import io.vertx.core.impl.resolver.DefaultResolverProvider; +import io.vertx.core.impl.VertxInternal; +import io.vertx.core.dns.impl.DnsAddressResolverProvider; +import io.vertx.core.dns.impl.DefaultAddressResolverProvider; import io.vertx.core.impl.logging.Logger; import io.vertx.core.impl.logging.LoggerFactory; @@ -28,25 +27,25 @@ /** * @author Julien Viet */ -public interface ResolverProvider { +public interface AddressResolverProvider { String DISABLE_DNS_RESOLVER_PROP_NAME = "vertx.disableDnsResolver"; - static ResolverProvider factory(Vertx vertx, AddressResolverOptions options) { + static AddressResolverProvider factory(Vertx vertx, AddressResolverOptions options) { // For now not really plugable, we just want to not fail when we can't load the async provider // that use an unstable API and fallback on the default (blocking) provider try { if (!Boolean.getBoolean(DISABLE_DNS_RESOLVER_PROP_NAME)) { - return new DnsResolverProvider((VertxImpl) vertx, options); + return DnsAddressResolverProvider.create((VertxInternal) vertx, options); } } catch (Throwable e) { if (e instanceof VertxException) { throw e; } - Logger logger = LoggerFactory.getLogger(ResolverProvider.class); + Logger logger = LoggerFactory.getLogger(AddressResolverProvider.class); logger.info("Using the default address resolver as the dns resolver could not be loaded"); } - return new DefaultResolverProvider(); + return new DefaultAddressResolverProvider(); } AddressResolverGroup resolver(AddressResolverOptions options); diff --git a/src/main/java/io/vertx/core/spi/resolver/endpoint/EndpointLookup.java b/src/main/java/io/vertx/core/spi/resolver/endpoint/EndpointLookup.java new file mode 100644 index 00000000000..2c110eb6b00 --- /dev/null +++ b/src/main/java/io/vertx/core/spi/resolver/endpoint/EndpointLookup.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.spi.resolver.endpoint; + +import io.vertx.core.net.SocketAddress; + +/** + * A lookup of an endpoint. + * + * @author Julien Viet + */ +public interface EndpointLookup { + + /** + * @return the endpoint socket address + */ + SocketAddress address(); + + /** + * Initiate a request with the endpoint, the returned endpoint request updates the endpoint statistics + * + * @return the request + */ + EndpointRequest initiateRequest(); + +} diff --git a/src/main/java/io/vertx/core/spi/resolver/endpoint/EndpointRequest.java b/src/main/java/io/vertx/core/spi/resolver/endpoint/EndpointRequest.java new file mode 100644 index 00000000000..e18dea6f577 --- /dev/null +++ b/src/main/java/io/vertx/core/spi/resolver/endpoint/EndpointRequest.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.spi.resolver.endpoint; + +/** + * Request interaction with an endpoint, mostly callbacks to gather statistics + * + * @author Julien Viet + */ +public interface EndpointRequest { + + /** + * Report a failure. + * @param failure the failure to report + */ + void reportFailure(Throwable failure); + + /** + * The request has begun. + */ + void reportRequestBegin(); + + /** + * The request has ended. + */ + void reportRequestEnd(); + + /** + * The response has begun. + */ + void reportResponseBegin(); + + /** + * The request has ended. + */ + void reportResponseEnd(); + +} diff --git a/src/main/java/io/vertx/core/spi/resolver/endpoint/EndpointResolver.java b/src/main/java/io/vertx/core/spi/resolver/endpoint/EndpointResolver.java new file mode 100644 index 00000000000..5526950dda5 --- /dev/null +++ b/src/main/java/io/vertx/core/spi/resolver/endpoint/EndpointResolver.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.spi.resolver.endpoint; + +import io.vertx.core.Future; +import io.vertx.core.impl.ContextInternal; +import io.vertx.core.net.Address; + +/** + * A resolver for endpoints. + * + * @author Julien Viet + */ +public interface EndpointResolver { + + /** + * Check whether the resolver accepts the {@code Address} and returns the cast address. + * + * @param address the addrss to check + * @return the cast address + */ + A accepts(Address address); + + /** + * Lookup an endpoint for the specified {@code address} + * @param ctx the vertx context + * @param address the address to lookup + * @return the endpoint lookup result + */ + Future lookupEndpoint(ContextInternal ctx, A address); + + /** + * Check expired endpoints, this method is called by the client periodically to give the opportunity to trigger eviction + * or refreshes. + */ + void checkExpired(); + +} diff --git a/src/main/java/io/vertx/core/spi/tls/DefaultSslContextFactory.java b/src/main/java/io/vertx/core/spi/tls/DefaultSslContextFactory.java index f71885b4f1d..ef606ef2024 100755 --- a/src/main/java/io/vertx/core/spi/tls/DefaultSslContextFactory.java +++ b/src/main/java/io/vertx/core/spi/tls/DefaultSslContextFactory.java @@ -143,10 +143,20 @@ private SslContext createContext(boolean useAlpn, boolean client, KeyManagerFact builder.ciphers(cipherSuites); } if (useAlpn && applicationProtocols != null && applicationProtocols.size() > 0) { + ApplicationProtocolConfig.SelectorFailureBehavior sfb; + ApplicationProtocolConfig.SelectedListenerFailureBehavior slfb; + if (sslProvider == SslProvider.JDK) { + sfb = ApplicationProtocolConfig.SelectorFailureBehavior.FATAL_ALERT; + slfb = ApplicationProtocolConfig.SelectedListenerFailureBehavior.FATAL_ALERT; + } else { + // Fatal alert not supportd by OpenSSL + sfb = ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE; + slfb = ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT; + } builder.applicationProtocolConfig(new ApplicationProtocolConfig( ApplicationProtocolConfig.Protocol.ALPN, - ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, - ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, + sfb, + slfb, applicationProtocols )); } diff --git a/src/main/java/io/vertx/core/spi/transport/Transport.java b/src/main/java/io/vertx/core/spi/transport/Transport.java index 804c2967d1f..4ada552de89 100644 --- a/src/main/java/io/vertx/core/spi/transport/Transport.java +++ b/src/main/java/io/vertx/core/spi/transport/Transport.java @@ -145,7 +145,7 @@ default void configure(DatagramChannel channel, DatagramSocketOptions options) { } } - default void configure(ClientOptionsBase options, boolean domainSocket, Bootstrap bootstrap) { + default void configure(ClientOptionsBase options, int connectTimeout, boolean domainSocket, Bootstrap bootstrap) { if (!domainSocket) { bootstrap.option(ChannelOption.SO_REUSEADDR, options.isReuseAddress()); bootstrap.option(ChannelOption.TCP_NODELAY, options.isTcpNoDelay()); @@ -167,7 +167,7 @@ default void configure(ClientOptionsBase options, boolean domainSocket, Bootstra if (options.getTrafficClass() != -1) { bootstrap.option(ChannelOption.IP_TOS, options.getTrafficClass()); } - bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, options.getConnectTimeout()); + bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout); } default void configure(NetServerOptions options, boolean domainSocket, ServerBootstrap bootstrap) { diff --git a/src/main/java/io/vertx/core/streams/Pump.java b/src/main/java/io/vertx/core/streams/Pump.java deleted file mode 100644 index a6fa68d06d1..00000000000 --- a/src/main/java/io/vertx/core/streams/Pump.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.streams; - -import io.vertx.codegen.annotations.Fluent; -import io.vertx.codegen.annotations.VertxGen; -import io.vertx.core.streams.impl.PumpImpl; - -/** - * Pumps data from a {@link ReadStream} to a {@link WriteStream} and performs flow control where necessary to - * prevent the write stream buffer from getting overfull. - *

- * Instances of this class read items from a {@link ReadStream} and write them to a {@link WriteStream}. If data - * can be read faster than it can be written this could result in the write queue of the {@link WriteStream} growing - * without bound, eventually causing it to exhaust all available RAM. - *

- * To prevent this, after each write, instances of this class check whether the write queue of the {@link - * WriteStream} is full, and if so, the {@link ReadStream} is paused, and a {@code drainHandler} is set on the - * {@link WriteStream}. - *

- * When the {@link WriteStream} has processed half of its backlog, the {@code drainHandler} will be - * called, which results in the pump resuming the {@link ReadStream}. - *

- * This class can be used to pump from any {@link ReadStream} to any {@link WriteStream}, - * e.g. from an {@link io.vertx.core.http.HttpServerRequest} to an {@link io.vertx.core.file.AsyncFile}, - * or from {@link io.vertx.core.net.NetSocket} to a {@link io.vertx.core.http.WebSocket}. - *

- * Please see the documentation for more information. - * - * @author Tim Fox - */ -@VertxGen -public interface Pump { - - /** - * Create a new {@code Pump} with the given {@code ReadStream} and {@code WriteStream} - * - * @param rs the read stream - * @param ws the write stream - * @return the pump - */ - static Pump pump(ReadStream rs, WriteStream ws) { - return new PumpImpl<>(rs, ws); - } - - /** - * Create a new {@code Pump} with the given {@code ReadStream} and {@code WriteStream} and - * {@code writeQueueMaxSize} - * - * @param rs the read stream - * @param ws the write stream - * @param writeQueueMaxSize the max size of the write queue - * @return the pump - */ - static Pump pump(ReadStream rs, WriteStream ws, int writeQueueMaxSize) { - return new PumpImpl<>(rs, ws, writeQueueMaxSize); - } - - /** - * Set the write queue max size to {@code maxSize} - * - * @param maxSize the max size - * @return a reference to this, so the API can be used fluently - */ - @Fluent - Pump setWriteQueueMaxSize(int maxSize); - - /** - * Start the Pump. The Pump can be started and stopped multiple times. - * - * @return a reference to this, so the API can be used fluently - */ - @Fluent - Pump start(); - - /** - * Stop the Pump. The Pump can be started and stopped multiple times. - * - * @return a reference to this, so the API can be used fluently - */ - @Fluent - Pump stop(); - - /** - * Return the total number of items pumped by this pump. - */ - int numberPumped(); - -} diff --git a/src/main/java/io/vertx/core/streams/impl/OutboundWriteQueue.java b/src/main/java/io/vertx/core/streams/impl/OutboundWriteQueue.java new file mode 100644 index 00000000000..44ac10de3b5 --- /dev/null +++ b/src/main/java/io/vertx/core/streams/impl/OutboundWriteQueue.java @@ -0,0 +1,370 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.streams.impl; + +import io.netty.util.internal.PlatformDependent; +import io.vertx.core.impl.Arguments; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import java.util.function.Predicate; + +/** + * A concurrent multi producers back-pressured queue fronting a single consumer back-pressured system. + * + * The queue lets multiple producers emit elements ({@link #add}/{@link #submit}) to the consumer with back-pressure + * to signal producers when they should stop emitting. + * + *

Handling elements

+ * + * The consumer side uses a {@link Predicate} to handle elements and decide whether it can accept them. When a + * consumer returns {@code false} it refuses the element and the queue will propose this element later when the + * consumer signals it can accept elements again. + * + *

Emitting elements

+ * + * Elements are emitted with {@link #add} and {@link #submit} methods. + * + *

Adding elements

+ * + * Only the consumer thread can add elements. When the consumer threads adds an element to the queue it tries + * to get the ownership of the queue and directly invoke the {@link #consumer}. When ownership + * is not acquired, the element is added to the queue, until it is handled by the consumer thread later. + * + *

Submitting elements

+ * + * When a producer thread submits an element to the queue, the element is added to the queue to let the consumer + * thread handle it. + * + *

Queue ownership

+ * + * Some operation require to own the queue to execute them. We already mentioned that {@link #add} and {@link #submit} + * attempts to get ownership of the queue, sometimes these methods will not release ownership when they exit, because the queue + * expects another thread or another condition to be met to make progress. Emitting methods can return a signal indicating that + * the queue should be drained to make progress: {@link #DRAIN_REQUIRED_MASK} signals the queue contains element that + * shall be drained. + * + *

Draining elements

+ * + * The {@link #drain} method tries to remove elements from the queue until the consumer refuses them or the queue becomes empty. + * When a method returns a {@link #DRAIN_REQUIRED_MASK} signal, the {@link #drain()} should be called by the consumer thread + * + *
    + *
  • A consumer method ({@link #add}, {@link #drain}) emits this signal when the {@link #consumer} refuses an element, + * therefore {@link #drain()} should be called when the consumer can accept element again
  • + *
  • A producer method ({@link #submit}) emits this signal when it acquired the ownership of the queue to {@link #drain} the + * queue.
  • + *
+ * + *

Back-pressure

+ * + *

Producers emission flow cannot reliably be controlled by the consumer back-pressure probe since producers + * emissions are transferred to the consumer thread: the consumer back-pressure controller does not take in account the + * inflight elements between producers and consumer. This queue is designed to provide reliable signals to control + * producers emission based on the consumer back-pressure probe and the number of inflight elements between + * producers and the consumer.

+ * + * The queue maintains an internal queue of elements initially empty and can be filled when + *
    + *
  • producer threads {@link #submit)} to the queue above the {@link #highWaterMark}
  • + *
  • the {@link #consumer} refuses an element
  • + *
+ * + *

When the internal queue grows above the {@link #highWaterMark}, the queue is considered as {@code unwritable}. + * {@link #add}/{@link #submit} methods return {@link #QUEUE_UNWRITABLE_MASK} to signal producers should stop emitting.

+ * + *

After a drain if the internal queue has shrunk under {@link #lowWaterMark}, the queue is considered as {@code writable}. + * {@link #add}/{@link #drain} methods return {@link #QUEUE_WRITABLE_MASK} to signal producers should start emitting. Note + * that the consumer thread handles this signal and should forward it to the producers.

+ * + *

When {@link #QUEUE_WRITABLE_MASK} is signalled, the number of {@link #QUEUE_UNWRITABLE_MASK} signals emitted is encoded + * in the flags. This number allows the producer flow controller to correctly account the producer writability: + * {@link #QUEUE_WRITABLE_MASK}/{@link #QUEUE_UNWRITABLE_MASK} signals are observed by different threads, therefore a good + * practice to compute the queue writability is to increment/decrement an atomic counter for each signal received.

+ */ +public class OutboundWriteQueue { + + /** + * Returns the number of times {@link #QUEUE_UNWRITABLE_MASK} signals encoded in {@code value} + * + * @param value the value + * @return then number of unwritable signals + */ + public static int numberOfUnwritableSignals(int value) { + return (value & ~0XF) >> 4; + } + + /** + * When the masked bit is set, the queue became unwritable, this triggers only when the queue transitions + * from the writable> state to the unwritable> state. + */ + public static final int QUEUE_UNWRITABLE_MASK = 0x01; + + /** + * When the masked bit is set, the queue became writable, this triggers only when the queue transitions + * from the unwritable> state to the writable state. + */ + public static final int QUEUE_WRITABLE_MASK = 0x02; + + /** + * When the masked bit is set, the caller has acquired the ownership of the queue and must drain it + * to attempt to release the ownership. + */ + public static final int DRAIN_REQUIRED_MASK = 0x04; + + /** + * The default high-water mark value: {@code 16} + */ + public static final int DEFAULT_HIGH_WATER_MARK = 16; + + /** + * The default low-water mark value: {@code 8} + */ + public static final int DEFAULT_LOW_WATER_MARK = 8; + + private static final AtomicLongFieldUpdater> WIP_UPDATER = (AtomicLongFieldUpdater>) (AtomicLongFieldUpdater)AtomicLongFieldUpdater.newUpdater(OutboundWriteQueue.class, "wip"); + + // Immutable + + private final Predicate consumer; + private final long highWaterMark; + private final long lowWaterMark; + + // Concurrent part accessed by any producer thread + + private final Queue queue = PlatformDependent.newMpscQueue(); + private volatile long wip = 0L; + + // Consumer thread only + + // The element refused by the consumer (null <=> overflow) + private E overflow; + // The number of times the queue was observed to be unwritable + private long writeQueueFull; + + /** + * Create a new instance. + * + * @param consumer the predicate accepting the elements + * @throws NullPointerException if consumer is null + */ + public OutboundWriteQueue(Predicate consumer) { + this(consumer, DEFAULT_LOW_WATER_MARK, DEFAULT_HIGH_WATER_MARK); + } + + /** + * Create a new instance. + * + * @param consumer the predicate accepting the elements + * @param lowWaterMark the low-water mark, must be zero or positive + * @param highWaterMark the high-water mark, must be greater than the low-water mark + * + * @throws NullPointerException if consumer is null + * @throws IllegalArgumentException if any mark violates the condition + */ + public OutboundWriteQueue(Predicate consumer, long lowWaterMark, long highWaterMark) { + Arguments.require(lowWaterMark >= 0, "The low-water mark must be >= 0"); + Arguments.require(lowWaterMark <= highWaterMark, "The high-water mark must greater or equals to the low-water mark"); + this.consumer = Objects.requireNonNull(consumer, "Consumer must be not null"); + this.lowWaterMark = lowWaterMark; + this.highWaterMark = highWaterMark; + } + + /** + * @return the queue high-water mark + */ + public long highWaterMark() { + return highWaterMark; + } + + /** + * @return the queue low-water mark + */ + public long lowWaterMark() { + return lowWaterMark; + } + + /** + * Let the consumer thread add the {@code element} to the queue. + * + * A set of flags is returned + *
    + *
  • When {@link #QUEUE_UNWRITABLE_MASK} is set, the queue is writable and new elements can be added to the queue, + * otherwise no elements should be added to the queue nor submitted but it is a soft condition
  • + *
  • When {@link #DRAIN_REQUIRED_MASK} is set, the queue overflow
  • + *
+ * + * @param element the element to add + * @return a bitset of [{@link #DRAIN_REQUIRED_MASK}, {@link #QUEUE_UNWRITABLE_MASK}, {@link #QUEUE_WRITABLE_MASK}] flags + */ + public int add(E element) { + if (WIP_UPDATER.compareAndSet(this, 0, 1)) { + if (!consumer.test(element)) { + overflow = element; + return DRAIN_REQUIRED_MASK | (WIP_UPDATER.get(this) == highWaterMark ? QUEUE_UNWRITABLE_MASK : 0); + } + if (consume(1) == 0) { + return 0; + } + return drainLoop(); + } else { + queue.add(element); + long v = WIP_UPDATER.incrementAndGet(this); + if (v == 1) { + return drainLoop(); + } else { + return v == highWaterMark ? QUEUE_UNWRITABLE_MASK : 0; + } + } + } + + /** + * Let a producer thread submit an {@code element} to the queue, no delivery is attempted. Submitting an element + * might acquire the ownership of the queue, when it happens a {@link #drain} operation should be called + * to drain the queue and release the ownership. + * + * @param element the element to submit + * @return a bitset of [{@link {@link #QUEUE_UNWRITABLE_MASK }, {@link #DRAIN_REQUIRED_MASK }}] flags + */ + public int submit(E element) { + queue.add(element); + // winning this => overflow == null + long pending = WIP_UPDATER.incrementAndGet(this); + if (pending == highWaterMark) { + hook2(); + } + return (pending == highWaterMark ? QUEUE_UNWRITABLE_MASK : 0) + ((pending == 1) ? DRAIN_REQUIRED_MASK : 0); + } + + protected void hook2() { + + } + + /** + * Let the consumer thread drain the queue until it becomes not writable or empty, this requires + * the ownership of the queue acquired by {@link #DRAIN_REQUIRED_MASK} flag. + * + * @return a bitset of [{@link #DRAIN_REQUIRED_MASK}, {@link #QUEUE_WRITABLE_MASK}] flags + */ + public int drain() { + E elt = overflow; + if (elt != null) { + if (!consumer.test(overflow)) { + return DRAIN_REQUIRED_MASK; + } + overflow = null; + if (consume(1) == 0) { + return 0; + } + } + hook(); + return drainLoop(); + } + + /** + * The main drain loop, entering this loop requires a few conditions: + *
    + *
  • {@link #overflow} must be {@code null}, the consumer still accepts elements
  • + *
  • {@link #wip} is greater than zero (ownership of the queue)
  • + *
  • only the consumer thread can execute it
  • + *
+ * + * The loop drains elements from the queue until + * + *
    + *
  • the queue is empty (wip == 0) which releases the queue ownership
  • + *
  • the {@link #consumer} rejects an element
  • + *
+ * + * When the {@link #consumer} rejects an element, the rejected element is parked + * in the {@link #overflow} field and the queue ownership is not released. At this point + * the {@link #drain()} shall be called to try again to drain the queue. + * + * @return a bitset of [{@link {@link #QUEUE_WRITABLE_MASK }, {@link #CONSUMER_PAUSED_MASK }}] flags + */ + // Note : we can optimize this by passing pending as argument of this method to avoid the initial + private int drainLoop() { + long pending = WIP_UPDATER.get(this); + if (pending == 0) { + throw new IllegalStateException(); + } + do { + int consumed; + for (consumed = 0; consumed < pending; consumed++) { + E elt = queue.poll(); + if (!consumer.test(elt)) { + overflow = elt; + break; + } + } + // the trick is to decrement the wip at once on each iteration in order to count the number of times producers + // have observed a QUEUE_UNWRITABLE_MASK signal + pending = consume(consumed); + } while (pending != 0 && overflow == null); + boolean writabilityChanged = pending < lowWaterMark && writeQueueFull > 0; + long val = writeQueueFull << 4; + if (writabilityChanged) { + writeQueueFull = 0; + } + int flags = 0; + flags |= overflow != null ? DRAIN_REQUIRED_MASK : 0; + flags |= writabilityChanged ? QUEUE_WRITABLE_MASK : 0; + flags |= val; + return flags; + } + + /** + * Consume a number of elements from the queue, this method updates the queue {@link #writeQueueFull} counter. + * + * @param amount the amount to consume + * @return the number of pending elements after consuming from the queue + */ + private long consume(int amount) { + long pending = WIP_UPDATER.addAndGet(this, -amount); + long size = pending + amount; + if (size >= highWaterMark && (size - amount) < highWaterMark) { + writeQueueFull++; + } + return pending; + } + + protected void hook() { + + } + + /** + * Clear the queue and return all the removed elements. + * + * @return the removed elements. + */ + public final List clear() { + writeQueueFull = 0; + List elts = new ArrayList<>(); + if (overflow != null) { + elts.add(overflow); + overflow = null; + if (WIP_UPDATER.decrementAndGet(this) == 0) { + return elts; + } + } + for (long pending = WIP_UPDATER.get(this);pending != 0;pending = WIP_UPDATER.addAndGet(this, -pending)) { + for (int i = 0;i < pending;i++) { + elts.add(queue.poll()); + } + } + return elts; + } +} diff --git a/src/main/java/io/vertx/core/streams/impl/PumpImpl.java b/src/main/java/io/vertx/core/streams/impl/PumpImpl.java deleted file mode 100644 index e8bb6943816..00000000000 --- a/src/main/java/io/vertx/core/streams/impl/PumpImpl.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.streams.impl; - -import io.vertx.core.Handler; -import io.vertx.core.streams.Pump; -import io.vertx.core.streams.ReadStream; -import io.vertx.core.streams.WriteStream; - -import java.util.Objects; - -/** - * Pumps data from a {@link io.vertx.core.streams.ReadStream} to a {@link io.vertx.core.streams.WriteStream} and performs flow control where necessary to - * prevent the write stream buffer from getting overfull.

- * Instances of this class read bytes from a {@link io.vertx.core.streams.ReadStream} and write them to a {@link io.vertx.core.streams.WriteStream}. If data - * can be read faster than it can be written this could result in the write queue of the {@link io.vertx.core.streams.WriteStream} growing - * without bound, eventually causing it to exhaust all available RAM.

- * To prevent this, after each write, instances of this class check whether the write queue of the {@link - * io.vertx.core.streams.WriteStream} is full, and if so, the {@link io.vertx.core.streams.ReadStream} is paused, and a {@code drainHandler} is set on the - * {@link io.vertx.core.streams.WriteStream}. When the {@link io.vertx.core.streams.WriteStream} has processed half of its backlog, the {@code drainHandler} will be - * called, which results in the pump resuming the {@link io.vertx.core.streams.ReadStream}.

- * This class can be used to pump from any {@link io.vertx.core.streams.ReadStream} to any {@link io.vertx.core.streams.WriteStream}, - * e.g. from an {@link io.vertx.core.http.HttpServerRequest} to an {@link io.vertx.core.file.AsyncFile}, - * or from {@link io.vertx.core.net.NetSocket} to a {@link io.vertx.core.http.WebSocket}.

- * - * Instances of this class are not thread-safe.

- * - * @author Tim Fox - */ -public class PumpImpl implements Pump { - - private final ReadStream readStream; - private final WriteStream writeStream; - private final Handler dataHandler; - private final Handler drainHandler; - private int pumped; - - /** - * Create a new {@code Pump} with the given {@code ReadStream} and {@code WriteStream}. Set the write queue max size - * of the write stream to {@code maxWriteQueueSize} - */ - public PumpImpl(ReadStream rs, WriteStream ws, int maxWriteQueueSize) { - this(rs, ws); - this.writeStream.setWriteQueueMaxSize(maxWriteQueueSize); - } - - public PumpImpl(ReadStream rs, WriteStream ws) { - Objects.requireNonNull(rs); - Objects.requireNonNull(ws); - this.readStream = rs; - this.writeStream = ws; - drainHandler = v-> readStream.resume(); - dataHandler = data -> { - writeStream.write(data); - incPumped(); - if (writeStream.writeQueueFull()) { - readStream.pause(); - writeStream.drainHandler(drainHandler); - } - }; - } - - /** - * Set the write queue max size to {@code maxSize} - */ - @Override - public PumpImpl setWriteQueueMaxSize(int maxSize) { - writeStream.setWriteQueueMaxSize(maxSize); - return this; - } - - /** - * Start the Pump. The Pump can be started and stopped multiple times. - */ - @Override - public PumpImpl start() { - readStream.handler(dataHandler); - return this; - } - - /** - * Stop the Pump. The Pump can be started and stopped multiple times. - */ - @Override - public PumpImpl stop() { - writeStream.drainHandler(null); - readStream.handler(null); - return this; - } - - /** - * Return the total number of elements pumped by this pump. - */ - @Override - public synchronized int numberPumped() { - return pumped; - } - - // Note we synchronize as numberPumped can be called from a different thread however incPumped will always - // be called from the same thread so we benefit from bias locked optimisation which should give a very low - // overhead - private synchronized void incPumped() { - pumped++; - } - - -} diff --git a/src/main/java/io/vertx/core/tracing/TracingOptions.java b/src/main/java/io/vertx/core/tracing/TracingOptions.java index 3fe5236af96..94b581878bd 100644 --- a/src/main/java/io/vertx/core/tracing/TracingOptions.java +++ b/src/main/java/io/vertx/core/tracing/TracingOptions.java @@ -12,6 +12,7 @@ package io.vertx.core.tracing; import io.vertx.codegen.annotations.DataObject; +import io.vertx.codegen.json.annotations.JsonGen; import io.vertx.core.json.JsonObject; import io.vertx.core.spi.VertxTracerFactory; @@ -21,11 +22,11 @@ * * @author Julien Viet */ -@DataObject(generateConverter = true, publicConverter = false) +@DataObject +@JsonGen(publicConverter = false) public class TracingOptions { private JsonObject json; // Keep a copy of the original json, so we don't lose info when building options subclasses - private VertxTracerFactory factory; /** * Default constructor @@ -39,7 +40,10 @@ public TracingOptions() { * @param other The other {@link TracingOptions} to copy when creating this */ public TracingOptions(TracingOptions other) { - factory = other.factory; + json = other.json; + if (json != null) { + json = json.copy(); + } } /** @@ -53,34 +57,6 @@ public TracingOptions(JsonObject json) { this.json = json.copy(); } - /** - * Get the tracer factory to be used when tracing are enabled. - *

- * If the tracer factory has been programmatically set here, then that will be used when tracing are enabled - * for creating the {@link io.vertx.core.spi.tracing.VertxTracer} instance. - *

- * Otherwise Vert.x attempts to locate a tracer factory implementation on the classpath. - * - * @return the tracer factory - */ - public VertxTracerFactory getFactory() { - return factory; - } - - /** - * Programmatically set the tracer factory to be used when tracing are enabled. - *

- * Normally Vert.x will look on the classpath for a tracer factory implementation, but if you want to set one - * programmatically you can use this method. - * - * @param factory the tracer factory - * @return a reference to this, so the API can be used fluently - */ - public TracingOptions setFactory(VertxTracerFactory factory) { - this.factory = factory; - return this; - } - public TracingOptions copy() { return new TracingOptions(this); } diff --git a/src/main/resources/META-INF/services/io.vertx.core.spi.launcher.CommandFactory b/src/main/resources/META-INF/services/io.vertx.core.spi.launcher.CommandFactory deleted file mode 100644 index d6e4603e433..00000000000 --- a/src/main/resources/META-INF/services/io.vertx.core.spi.launcher.CommandFactory +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2011-2017 Contributors to the Eclipse Foundation -# -# This program and the accompanying materials are made available under the -# terms of the Eclipse Public License 2.0 which is available at -# http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -# which is available at https://www.apache.org/licenses/LICENSE-2.0. -# -# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - -# Core commands -io.vertx.core.impl.launcher.commands.RunCommandFactory -io.vertx.core.impl.launcher.commands.VersionCommandFactory -io.vertx.core.impl.launcher.commands.BareCommandFactory - -# Background application control -io.vertx.core.impl.launcher.commands.ListCommandFactory -io.vertx.core.impl.launcher.commands.StartCommandFactory -io.vertx.core.impl.launcher.commands.StopCommandFactory diff --git a/src/test/benchmarks/io/vertx/benchmarks/ContextBenchmark.java b/src/test/benchmarks/io/vertx/benchmarks/ContextBenchmark.java index b0fdebc53a0..2b108c45694 100644 --- a/src/test/benchmarks/io/vertx/benchmarks/ContextBenchmark.java +++ b/src/test/benchmarks/io/vertx/benchmarks/ContextBenchmark.java @@ -14,6 +14,7 @@ import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.impl.BenchmarkContext; +import io.vertx.core.impl.ContextInternal; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.CompilerControl; import org.openjdk.jmh.annotations.Fork; @@ -35,7 +36,7 @@ public static void consume(final String buf) { public static class BaselineState { Vertx vertx; - BenchmarkContext context; + ContextInternal context; Handler task; @Setup diff --git a/src/test/benchmarks/io/vertx/benchmarks/JsonEncodeBenchmark.java b/src/test/benchmarks/io/vertx/benchmarks/JsonEncodeBenchmark.java index 57c4defb093..a7933181e31 100644 --- a/src/test/benchmarks/io/vertx/benchmarks/JsonEncodeBenchmark.java +++ b/src/test/benchmarks/io/vertx/benchmarks/JsonEncodeBenchmark.java @@ -12,25 +12,36 @@ package io.vertx.benchmarks; import com.fasterxml.jackson.databind.ObjectMapper; +import io.vertx.core.buffer.Buffer; import io.vertx.core.json.JsonObject; import io.vertx.core.json.jackson.DatabindCodec; import io.vertx.core.json.jackson.JacksonCodec; import io.vertx.core.spi.json.JsonCodec; -import org.openjdk.jmh.annotations.*; -import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.CompilerControl; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import static org.openjdk.jmh.annotations.CompilerControl.Mode.INLINE; +import static org.openjdk.jmh.annotations.Mode.*; import java.io.IOException; import java.net.URL; import java.util.Map; +import java.util.concurrent.TimeUnit; /** * @author Thomas Segismont * @author slinkydeveloper */ @State(Scope.Thread) -@BenchmarkMode(Mode.AverageTime) +@BenchmarkMode(AverageTime) +@OutputTimeUnit(TimeUnit.MICROSECONDS) public class JsonEncodeBenchmark extends BenchmarkBase { - + private JsonObject tiny; private JsonObject small; private JsonObject wide; private JsonObject deep; @@ -40,6 +51,7 @@ public class JsonEncodeBenchmark extends BenchmarkBase { @Setup public void setup() { ClassLoader classLoader = getClass().getClassLoader(); + tiny = new JsonObject(Map.of("message", "Hello, World!")); small = loadJson(classLoader.getResource("small_bench.json")); wide = loadJson(classLoader.getResource("wide_bench.json")); deep = loadJson(classLoader.getResource("deep_bench.json")); @@ -56,78 +68,87 @@ private JsonObject loadJson(URL url) { } @Benchmark - public void smallStringJackson(Blackhole blackhole) throws Exception { - stringJackson(small, blackhole); + public String smallStringJackson() { + return stringJackson(small); } @Benchmark - public void smallStringDatabind(Blackhole blackhole) throws Exception { - stringDatabind(small, blackhole); + public String smallStringDatabind() { + return stringDatabind(small); } @Benchmark - public void wideStringJackson(Blackhole blackhole) throws Exception { - stringJackson(wide, blackhole); + public String wideStringJackson() { + return stringJackson(wide); } @Benchmark - public void wideStringDatabind(Blackhole blackhole) throws Exception { - stringDatabind(wide, blackhole); + public String wideStringDatabind() { + return stringDatabind(wide); } @Benchmark - public void deepStringJackson(Blackhole blackhole) throws Exception { - stringJackson(deep, blackhole); + public String deepStringJackson() { + return stringJackson(deep); } @Benchmark - public void deepStringDatabind(Blackhole blackhole) throws Exception { - stringDatabind(deep, blackhole); + public String deepStringDatabind() { + return stringDatabind(deep); } - private void stringJackson(JsonObject jsonObject, Blackhole blackhole) throws Exception { - blackhole.consume(jsonObject.encode()); + @CompilerControl(INLINE) + private String stringJackson(JsonObject jsonObject) { + return jacksonCodec.toString(jsonObject); } - private void stringDatabind(JsonObject jsonObject, Blackhole blackhole) throws Exception { - blackhole.consume(databindCodec.toString(jsonObject)); + @CompilerControl(INLINE) + private String stringDatabind(JsonObject jsonObject) { + return databindCodec.toString(jsonObject); + } + + @Benchmark + public Buffer tinyBufferJackson() { + return bufferJackson(tiny); } @Benchmark - public void smallBufferJackson(Blackhole blackhole) throws Exception { - bufferJackson(small, blackhole); + public Buffer smallBufferJackson() { + return bufferJackson(small); } @Benchmark - public void smallBufferDatabind(Blackhole blackhole) throws Exception { - bufferDatabind(small, blackhole); + public Buffer smallBufferDatabind() { + return bufferDatabind(small); } @Benchmark - public void deepBufferJackson(Blackhole blackhole) throws Exception { - bufferJackson(deep, blackhole); + public Buffer deepBufferJackson() { + return bufferJackson(deep); } @Benchmark - public void deepBufferDatabind(Blackhole blackhole) throws Exception { - bufferDatabind(deep, blackhole); + public Buffer deepBufferDatabind() { + return bufferDatabind(deep); } @Benchmark - public void wideBufferJackson(Blackhole blackhole) throws Exception { - bufferJackson(wide, blackhole); + public Buffer wideBufferJackson() { + return bufferJackson(wide); } @Benchmark - public void wideBufferDatabind(Blackhole blackhole) throws Exception { - bufferDatabind(wide, blackhole); + public Buffer wideBufferDatabind() { + return bufferDatabind(wide); } - private void bufferJackson(JsonObject jsonObject, Blackhole blackhole) throws Exception { - blackhole.consume(jsonObject.toBuffer()); + @CompilerControl(INLINE) + private Buffer bufferJackson(JsonObject jsonObject) { + return jacksonCodec.toBuffer(jsonObject); } - private void bufferDatabind(JsonObject jsonObject, Blackhole blackhole) throws Exception { - blackhole.consume(jacksonCodec.toBuffer(jsonObject)); + @CompilerControl(INLINE) + private Buffer bufferDatabind(JsonObject jsonObject) { + return databindCodec.toBuffer(jsonObject); } } diff --git a/src/test/benchmarks/io/vertx/core/http/impl/HttpServerHandlerBenchmark.java b/src/test/benchmarks/io/vertx/core/http/impl/HttpServerHandlerBenchmark.java index b44f893a746..98c30ea7d1c 100644 --- a/src/test/benchmarks/io/vertx/core/http/impl/HttpServerHandlerBenchmark.java +++ b/src/test/benchmarks/io/vertx/core/http/impl/HttpServerHandlerBenchmark.java @@ -226,6 +226,7 @@ public void setup() { Http1xServerConnection conn = new Http1xServerConnection( () -> context, null, + null, new HttpServerOptions(), chctx, context, diff --git a/src/test/benchmarks/io/vertx/core/impl/BenchmarkContext.java b/src/test/benchmarks/io/vertx/core/impl/BenchmarkContext.java index 15a313ab464..c0076626eb1 100644 --- a/src/test/benchmarks/io/vertx/core/impl/BenchmarkContext.java +++ b/src/test/benchmarks/io/vertx/core/impl/BenchmarkContext.java @@ -11,72 +11,38 @@ package io.vertx.core.impl; -import io.vertx.core.Handler; +import io.vertx.core.ThreadingModel; import io.vertx.core.Vertx; -import java.util.concurrent.Executor; - /** * @author Julien Viet */ -public class BenchmarkContext extends ContextBase { - - public static BenchmarkContext create(Vertx vertx) { +public class BenchmarkContext { + + private static final EventExecutor EXECUTOR = new EventExecutor() { + @Override + public boolean inThread() { + throw new UnsupportedOperationException(); + } + @Override + public void execute(Runnable command) { + command.run(); + } + }; + + public static ContextInternal create(Vertx vertx) { VertxImpl impl = (VertxImpl) vertx; - return new BenchmarkContext( + return new ContextImpl( impl, + ThreadingModel.WORKER, + impl.getEventLoopGroup().next(), + EXECUTOR, impl.internalWorkerPool, impl.workerPool, + new TaskQueue(), + null, + null, Thread.currentThread().getContextClassLoader() ); } - - public BenchmarkContext(VertxInternal vertx, WorkerPool internalBlockingPool, WorkerPool workerPool, ClassLoader tccl) { - super(vertx, vertx.getEventLoopGroup().next(), internalBlockingPool, workerPool, null, null, tccl); - } - - @Override - public Executor executor() { - return Runnable::run; - } - - @Override - public boolean inThread() { - throw new UnsupportedOperationException(); - } - - @Override - protected void emit(ContextInternal ctx, T argument, Handler task) { - throw new UnsupportedOperationException(); - } - - @Override - protected void runOnContext(ContextInternal ctx, Handler action) { - ctx.dispatch(null, action); - } - - @Override - protected void execute(ContextInternal ctx, T argument, Handler task) { - task.handle(argument); - } - - @Override - protected void execute(ContextInternal ctx, Runnable task) { - task.run(); - } - - @Override - public void execute(Runnable task) { - task.run(); - } - - @Override - public boolean isEventLoopContext() { - return false; - } - - @Override - public boolean isWorkerContext() { - return false; - } } diff --git a/src/test/java/io/vertx/core/BlockedThreadCheckerTest.java b/src/test/java/io/vertx/core/BlockedThreadCheckerTest.java index 2879aaf097b..72f9bf0775a 100644 --- a/src/test/java/io/vertx/core/BlockedThreadCheckerTest.java +++ b/src/test/java/io/vertx/core/BlockedThreadCheckerTest.java @@ -15,7 +15,6 @@ import io.vertx.core.impl.btc.BlockedThreadEvent; import io.vertx.test.core.TestUtils; import io.vertx.test.core.VertxTestBase; -import org.junit.Assert; import org.junit.Test; import java.util.ArrayList; @@ -30,27 +29,28 @@ */ public class BlockedThreadCheckerTest extends VertxTestBase { - private volatile List events; + private final List events = Collections.synchronizedList(new ArrayList<>()); public void expectMessage(String poolName, long maxExecuteTime, TimeUnit maxExecuteTimeUnit) { - boolean match = events - .stream() - .anyMatch(event -> - event.thread().getName().startsWith(poolName) && - event.maxExecTime() == maxExecuteTimeUnit.toNanos(maxExecuteTime)); - Assert.assertTrue("Invalid events " + events, match); + List copy; + synchronized (events) { + copy = new ArrayList<>(events); + } + boolean match = false; + for (BlockedThreadEvent event : copy) { + if (event.thread().getName().startsWith(poolName) && + event.maxExecTime() == maxExecuteTimeUnit.toNanos(maxExecuteTime)) { + match = true; + break; + } + } + assertTrue("Invalid events: " + copy, match); } private void catchBlockedThreadEvents(Vertx vertx) { ((VertxInternal)vertx).blockedThreadChecker().setThreadBlockedHandler(event -> events.add(event)); } - @Override - public void setUp() throws Exception { - super.setUp(); - events = Collections.synchronizedList(new ArrayList<>()); - } - @Test public void testBlockCheckDefault() throws Exception { Verticle verticle = new AbstractVerticle() { @@ -115,7 +115,7 @@ public void start() throws InterruptedException { try { catchBlockedThreadEvents(newVertx); DeploymentOptions deploymentOptions = new DeploymentOptions(); - deploymentOptions.setWorker(true); + deploymentOptions.setThreadingModel(ThreadingModel.WORKER); newVertx.deployVerticle(verticle, deploymentOptions); await(); expectMessage("vert.x-worker-thread", maxWorkerExecuteTime, maxWorkerExecuteTimeUnit); @@ -129,13 +129,14 @@ public void testBlockCheckExecuteBlocking() throws Exception { Verticle verticle = new AbstractVerticle() { @Override public void start() throws InterruptedException { - vertx.executeBlocking(fut -> { + vertx.executeBlocking(() -> { try { Thread.sleep(3000); } catch (InterruptedException e) { fail(); } testComplete(); + return null; }); } }; @@ -169,13 +170,9 @@ public void testNamedWorkerPoolMaxExecuteWorkerTime() { vertx.deployVerticle(new AbstractVerticle() { @Override public void start(Promise startPromise) throws Exception { - vertx.executeBlocking(fut -> { - try { - SECONDS.sleep(5); - fut.complete(); - } catch (InterruptedException e) { - fut.fail(e); - } + vertx.executeBlocking(() -> { + SECONDS.sleep(5); + return null; }).onComplete(startPromise); } }, deploymentOptions).onComplete(onSuccess(did -> { @@ -212,7 +209,7 @@ public void start() throws InterruptedException { complete(); }); DeploymentOptions deploymentOptions = new DeploymentOptions(); - deploymentOptions.setWorker(true); + deploymentOptions.setThreadingModel(ThreadingModel.WORKER); newVertx.deployVerticle(verticle, deploymentOptions); await(); } diff --git a/src/test/java/io/vertx/core/ComplexHATest.java b/src/test/java/io/vertx/core/ComplexHATest.java index 9f2de286dd1..d1588192cb0 100644 --- a/src/test/java/io/vertx/core/ComplexHATest.java +++ b/src/test/java/io/vertx/core/ComplexHATest.java @@ -11,10 +11,6 @@ package io.vertx.core; -import io.vertx.core.DeploymentOptions; -import io.vertx.core.Vertx; -import io.vertx.core.VertxOptions; -import io.vertx.core.impl.ConcurrentHashSet; import io.vertx.core.impl.Deployment; import io.vertx.core.impl.VertxInternal; import io.vertx.core.json.JsonObject; @@ -29,6 +25,7 @@ import java.util.List; import java.util.Random; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -40,11 +37,12 @@ */ public class ComplexHATest extends VertxTestBase { + @Override protected ClusterManager getClusterManager() { return new FakeClusterManager(); } - private Random random = new Random(); + private final Random random = new Random(); protected final int maxVerticlesPerNode = 20; protected Set[] deploymentSnapshots; @@ -52,6 +50,7 @@ protected ClusterManager getClusterManager() { protected volatile int killedNode; protected List aliveNodes; + @Override public void setUp() throws Exception { super.setUp(); deploymentSnapshots = null; @@ -162,7 +161,7 @@ protected void takeDeploymentSnapshots() { } protected Set takeDeploymentSnapshot(int pos) { - Set snapshot = new ConcurrentHashSet<>(); + Set snapshot = ConcurrentHashMap.newKeySet(); VertxInternal v = (VertxInternal)vertices[pos]; for (String depID: v.deploymentIDs()) { snapshot.add(v.getDeployment(depID)); @@ -175,9 +174,9 @@ protected void kill(int pos) { takeDeploymentSnapshots(); VertxInternal v = (VertxInternal)vertices[pos]; killedNode = pos; - v.executeBlocking(fut -> { + v.executeBlocking(() -> { v.simulateKill(); - fut.complete(); + return null; }, false).onComplete(onSuccess(v2 -> {})); } diff --git a/src/test/java/io/vertx/core/ContextTest.java b/src/test/java/io/vertx/core/ContextTest.java index 62977189554..0822a505ed7 100644 --- a/src/test/java/io/vertx/core/ContextTest.java +++ b/src/test/java/io/vertx/core/ContextTest.java @@ -15,6 +15,7 @@ import io.vertx.core.impl.*; import io.vertx.core.impl.future.PromiseInternal; import io.vertx.test.core.VertxTestBase; +import org.junit.Assume; import org.junit.Test; import java.net.URL; @@ -124,12 +125,12 @@ public void testGettingContextContextUnderContextAnotherInstanceShouldReturnDiff @Test public void testExecuteOrderedBlocking() throws Exception { Context context = vertx.getOrCreateContext(); - context.executeBlocking(f -> { + context.executeBlocking(() -> { assertTrue(Context.isOnWorkerThread()); - f.complete(1 + 2); + return 1 + 2; }).onComplete(onSuccess(r -> { assertTrue(Context.isOnEventLoopThread()); - assertEquals(r, 3); + assertEquals((int)r, 3); testComplete(); })); await(); @@ -138,13 +139,13 @@ public void testExecuteOrderedBlocking() throws Exception { @Test public void testExecuteUnorderedBlocking() throws Exception { Context context = vertx.getOrCreateContext(); - context.executeBlocking(f -> { + context.executeBlocking(() -> { assertTrue(Context.isOnWorkerThread()); - f.complete(1 + 2); + return 1 + 2; }, false) .onComplete(onSuccess(r -> { assertTrue(Context.isOnEventLoopThread()); - assertEquals(r, 3); + assertEquals((int)r, 3); testComplete(); })); await(); @@ -155,7 +156,7 @@ public void testExecuteBlockingThreadSyncComplete() throws Exception { Context context = vertx.getOrCreateContext(); context.runOnContext(v -> { Thread expected = Thread.currentThread(); - context.executeBlocking(Promise::complete).onComplete(onSuccess(r -> { + context.executeBlocking(() -> null).onComplete(onSuccess(r -> { assertSame(expected, Thread.currentThread()); testComplete(); })); @@ -168,7 +169,8 @@ public void testExecuteBlockingThreadAsyncComplete() throws Exception { Context context = vertx.getOrCreateContext(); context.runOnContext(v -> { Thread expected = Thread.currentThread(); - context.executeBlocking(fut -> { + context.executeBlocking(() -> { + CountDownLatch latch = new CountDownLatch(1); new Thread(() -> { try { // Wait some time to allow the worker thread to set the handler on the future and have the future @@ -177,8 +179,10 @@ public void testExecuteBlockingThreadAsyncComplete() throws Exception { } catch (InterruptedException e) { Thread.currentThread().interrupt(); } - fut.complete(); + latch.countDown(); }).start(); + latch.await(20, TimeUnit.SECONDS); + return null; }).onComplete(onSuccess(r -> { assertSame(context, Vertx.currentContext()); assertSame(expected, Thread.currentThread()); @@ -297,30 +301,26 @@ private void testVerticleUseDifferentOrderedExecutor(boolean worker) throws Exce vertx.deployVerticle(new AbstractVerticle() { @Override public void start() throws Exception { - vertx.executeBlocking(fut -> { + vertx.executeBlocking(() -> { latch1.countDown(); - try { - awaitLatch(latch2); - fut.complete(); - } catch (InterruptedException e) { - fut.fail(e); - } + awaitLatch(latch2); + return null; }).onComplete(onSuccess(v -> complete())); } - }, new DeploymentOptions().setWorker(worker)); + }, new DeploymentOptions().setThreadingModel(ThreadingModel.WORKER)); awaitLatch(latch1); CountDownLatch latch3 = new CountDownLatch(1); vertx.deployVerticle(new AbstractVerticle() { @Override public void start() throws Exception { - vertx.executeBlocking(fut -> { + vertx.executeBlocking(() -> { latch3.countDown(); - fut.complete(); + return null; }).onComplete(onSuccess(v -> { complete(); })); } - }, new DeploymentOptions().setWorker(worker)); + }, new DeploymentOptions().setThreadingModel(ThreadingModel.WORKER)); awaitLatch(latch3); latch2.countDown(); await(); @@ -329,7 +329,7 @@ public void start() throws Exception { @Test public void testInternalExecuteBlockingWithQueue() { ContextInternal context = (ContextInternal) vertx.getOrCreateContext(); - List>>> lst = new ArrayList<>(); + List>> lst = new ArrayList<>(); for (int i = 0;i < 2;i++) { TaskQueue queue = new TaskQueue(); lst.add(task -> { @@ -339,7 +339,7 @@ public void testInternalExecuteBlockingWithQueue() { testInternalExecuteBlockingWithQueue(lst); } - public void testInternalExecuteBlockingWithQueue(List>>> lst) { + public void testInternalExecuteBlockingWithQueue(List>> lst) { AtomicReference[] current = new AtomicReference[lst.size()]; waitFor(lst.size()); for (int i = 0;i < current.length;i++) { @@ -352,7 +352,7 @@ public void testInternalExecuteBlockingWithQueue(List> task = fut -> { + Callable task = () -> { if (ival == 0) { current[jval].set(Thread.currentThread()); latch.countDown(); @@ -372,6 +372,7 @@ public void testInternalExecuteBlockingWithQueue(List { - vertx.executeBlocking(Promise::complete).onComplete(onSuccess(res -> { + vertx.executeBlocking(() -> null).onComplete(onSuccess(res -> { assertSame(duplicated, Vertx.currentContext()); latch4.countDown(); })); @@ -564,7 +565,7 @@ private void testDuplicateExecuteBlocking(Supplier supplier, bo int n = 2; List dup1 = Stream.generate(supplier).limit(n).collect(Collectors.toList()); AtomicInteger cnt = new AtomicInteger(); - List> futures = dup1.stream().map(c -> c.executeBlocking(duplicate -> { + List> futures = dup1.stream().map(c -> c.executeBlocking(() -> { assertTrue(Context.isOnWorkerThread()); int val = cnt.incrementAndGet(); if (ordered) { @@ -579,7 +580,7 @@ private void testDuplicateExecuteBlocking(Supplier supplier, bo } finally { cnt.decrementAndGet(); } - duplicate.complete(); + return null; }, ordered)).collect(Collectors.toList()); Future.all(futures).onComplete(onSuccess(v -> { testComplete(); @@ -699,15 +700,17 @@ void testEventLoopContextPromiseCompletedByAnotherEventLoopThread(Consumer p.complete("the-value")); + testEventLoopContextPromiseCompletedByWorkerThread(() -> "the-value"); } @Test public void testEventLoopContextPromiseFailedByWorkerThread() { - testEventLoopContextPromiseCompletedByWorkerThread(p -> p.fail(new Exception())); + testEventLoopContextPromiseCompletedByWorkerThread(() -> { + throw new Exception(); + }); } - private void testEventLoopContextPromiseCompletedByWorkerThread(Consumer> action) { + private void testEventLoopContextPromiseCompletedByWorkerThread(Callable action) { ContextInternal context = (ContextInternal) vertx.getOrCreateContext(); Promise promise = context.promise(); context.runOnContext(v -> { @@ -716,8 +719,16 @@ private void testEventLoopContextPromiseCompletedByWorkerThread(Consumer { - action.accept(promise); + context.executeBlocking(() -> { + String res; + try { + res = action.call(); + } catch (Exception e) { + promise.fail(e); + return null; + } + promise.tryComplete(res); + return null; }); }); await(); @@ -978,4 +989,69 @@ public void testDispatchContextOnAnyThread() { assertSame(current, thread.getContextClassLoader()); assertEquals(2, exec.get()); } + + @Test + public void testAwaitFromEventLoopThread() { + testAwaitFromContextThread(ThreadingModel.EVENT_LOOP, true); + } + + @Test + public void testAwaitFromWorkerThread() { + testAwaitFromContextThread(ThreadingModel.WORKER, false); + } + + @Test + public void testAwaitFromVirtualThreadThread() { + Assume.assumeTrue(VertxInternal.isVirtualThreadAvailable()); + testAwaitFromContextThread(ThreadingModel.VIRTUAL_THREAD, false); + } + + private void testAwaitFromContextThread(ThreadingModel threadMode, boolean fail) { + vertx.deployVerticle(() -> new AbstractVerticle() { + @Override + public void start() { + Promise promise = Promise.promise(); + vertx.setTimer(10, id -> promise.complete("foo")); + try { + String res = promise.future().await(); + assertFalse(fail); + assertEquals("foo", res); + } catch (IllegalStateException e) { + assertTrue(fail); + } + } + }, new DeploymentOptions().setThreadingModel(threadMode)).onComplete(onSuccess(v -> testComplete())); + await(); + } + + @Test + public void testInterruptThreadOnAwait() { + Assume.assumeTrue(VertxInternal.isVirtualThreadAvailable()); + vertx.deployVerticle(() -> new AbstractVerticle() { + @Override + public void start() { + Thread current = Thread.currentThread(); + Promise promise = Promise.promise(); + new Thread(() -> { + while (current.getState() != Thread.State.WAITING) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + current.interrupt(); + }).start(); + try { + Future.await(promise.future()); + fail(); + } catch (Exception expected) { + assertFalse(current.isInterrupted()); + assertEquals(expected.getClass(), InterruptedException.class); + testComplete(); + } + } + }, new DeploymentOptions().setThreadingModel(ThreadingModel.VIRTUAL_THREAD)); + await(); + } } diff --git a/src/test/java/io/vertx/core/CreateVertxTest.java b/src/test/java/io/vertx/core/CreateVertxTest.java index 815466dd409..b45e185e67c 100644 --- a/src/test/java/io/vertx/core/CreateVertxTest.java +++ b/src/test/java/io/vertx/core/CreateVertxTest.java @@ -11,6 +11,7 @@ package io.vertx.core; +import io.vertx.core.spi.cluster.ClusterManager; import io.vertx.test.core.VertxTestBase; import io.vertx.test.fakecluster.FakeClusterManager; import org.junit.Test; @@ -51,13 +52,13 @@ public void testCreateClusteredVertxAsync() { @Test public void testCreateClusteredVertxAsyncDetectJoinFailure() { - VertxOptions options = new VertxOptions().setClusterManager(new FakeClusterManager(){ + ClusterManager clusterManager = new FakeClusterManager(){ @Override public void join(Promise promise) { promise.fail("joinfailure"); } - }); - clusteredVertx(options, ar -> { + }; + clusteredVertx(new VertxOptions(), clusterManager, ar -> { assertTrue(ar.failed()); assertEquals("joinfailure", ar.cause().getMessage()); testComplete(); diff --git a/src/test/java/io/vertx/core/CustomerLauncherLowMemoryTest.java b/src/test/java/io/vertx/core/CustomerLauncherLowMemoryTest.java deleted file mode 100644 index 8ab83c513e1..00000000000 --- a/src/test/java/io/vertx/core/CustomerLauncherLowMemoryTest.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (c) 2011-2022 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - -import static io.vertx.test.core.AsyncTestBase.assertWaitUntil; - -public class CustomerLauncherLowMemoryTest { - - private static final String MSG_HOOK = CustomerLauncherLowMemoryTest.class.getSimpleName() + "-hook"; - - private Process process; - private File output; - - @Before - public void setUp() throws Exception { - output = File.createTempFile(CustomerLauncherLowMemoryTest.class.getSimpleName(), ".txt"); - output.deleteOnExit(); - } - - @After - public void tearDown() throws Exception { - if (process != null) { - process.destroyForcibly(); - } - } - - @Test - public void testCloseHookInvoked() throws Exception { - startExternalProcess(); - assertWaitUntil(() -> outputContains(MSG_HOOK), 10000, "Hook not invoked"); - stopExternalProcess(); - } - - private void startExternalProcess() throws IOException { - String javaHome = System.getProperty("java.home"); - String classpath = System.getProperty("java.class.path"); - - List command = new ArrayList<>(); - command.add(javaHome + File.separator + "bin" + File.separator + "java"); - command.add("-Xms100M"); - command.add("-Xmx100M"); - command.add("-classpath"); - command.add(classpath); - command.add(Launcher.class.getName()); - command.add("run"); - command.add(Verticle.class.getName()); - - process = new ProcessBuilder(command) - .redirectOutput(output) - .redirectErrorStream(true) - .start(); - } - - private void stopExternalProcess() throws InterruptedException { - AtomicBoolean stopped = new AtomicBoolean(); - new Thread(() -> { - try { - Thread.sleep(10_000); - } catch (InterruptedException ignore) { - return; - } - if (!stopped.get()) { - process.destroy(); - } - }); - process.waitFor(); - stopped.set(true); - } - - private boolean outputContains(String line) { - try { - return Files.readAllLines(output.toPath()).contains(line); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public static class Launcher extends io.vertx.core.Launcher { - - public static void main(String[] args) { - new Launcher().dispatch(args); - } - - @Override - public void beforeStoppingVertx(Vertx vertx) { - System.out.println(MSG_HOOK); - } - } - - public static class Verticle extends AbstractVerticle { - - private final Runtime runtime; - @SuppressWarnings("unused") - private List arrays; - - public Verticle() { - runtime = Runtime.getRuntime(); - } - - @Override - public void start() throws Exception { - vertx.>executeBlocking(prom -> { - List res = new ArrayList<>(); - long l; - do { - res.add(new byte[5 * 1024]); - l = runtime.freeMemory(); - } while (l > 15 * 1024 * 1024); - runtime.gc(); - try { - Thread.sleep(100); - prom.complete(res); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - prom.fail(e); - } - }).onComplete(ar1 -> { - if (ar1.succeeded()) { - arrays = ar1.result(); - context.owner().close(); - } else { - ar1.cause().printStackTrace(); - } - }); - } - } -} diff --git a/src/test/java/io/vertx/core/DeploymentTest.java b/src/test/java/io/vertx/core/DeploymentTest.java index ba7d0f381e5..b3fdb21d59c 100644 --- a/src/test/java/io/vertx/core/DeploymentTest.java +++ b/src/test/java/io/vertx/core/DeploymentTest.java @@ -53,9 +53,6 @@ public void testOptions() { JsonObject config = new JsonObject().put("foo", "bar").put("obj", new JsonObject().put("quux", 123)); assertEquals(options, options.setConfig(config)); assertEquals(config, options.getConfig()); - assertFalse(options.isWorker()); - assertEquals(options, options.setWorker(true)); - assertTrue(options.isWorker()); String rand = TestUtils.randomUnicodeString(1000); assertFalse(options.isHa()); assertEquals(options, options.setHa(true)); @@ -90,14 +87,12 @@ public void testCopyOptions() { long maxWorkerExecuteTime = TestUtils.randomPositiveLong(); TimeUnit maxWorkerExecuteTimeUnit = TimeUnit.MILLISECONDS; options.setConfig(config); - options.setWorker(worker); options.setHa(ha); options.setWorkerPoolName(poolName); options.setWorkerPoolSize(poolSize); options.setMaxWorkerExecuteTime(maxWorkerExecuteTime); options.setMaxWorkerExecuteTimeUnit(maxWorkerExecuteTimeUnit); DeploymentOptions copy = new DeploymentOptions(options); - assertEquals(worker, copy.isWorker()); assertNotSame(config, copy.getConfig()); assertEquals("bar", copy.getConfig().getString("foo")); assertEquals(ha, copy.isHa()); @@ -112,7 +107,6 @@ public void testDefaultJsonOptions() { DeploymentOptions def = new DeploymentOptions(); DeploymentOptions json = new DeploymentOptions(new JsonObject()); assertEquals(def.getConfig(), json.getConfig()); - assertEquals(def.isWorker(), json.isWorker()); assertEquals(def.isHa(), json.isHa()); assertEquals(def.getWorkerPoolName(), json.getWorkerPoolName()); assertEquals(def.getWorkerPoolSize(), json.getWorkerPoolSize()); @@ -141,7 +135,6 @@ public void testJsonOptions() { json.put("maxWorkerExecuteTime", maxWorkerExecuteTime); json.put("maxWorkerExecuteTimeUnit", maxWorkerExecuteTimeUnit); DeploymentOptions options = new DeploymentOptions(json); - assertEquals(worker, options.isWorker()); assertEquals("bar", options.getConfig().getString("foo")); assertEquals(ha, options.isHa()); assertEquals(poolName, options.getWorkerPoolName()); @@ -164,7 +157,6 @@ public void testToJson() { long maxWorkerExecuteTime = TestUtils.randomPositiveLong(); TimeUnit maxWorkerExecuteTimeUnit = TimeUnit.MILLISECONDS; options.setConfig(config); - options.setWorker(worker); options.setHa(ha); options.setWorkerPoolName(poolName); options.setWorkerPoolSize(poolSize); @@ -172,7 +164,6 @@ public void testToJson() { options.setMaxWorkerExecuteTimeUnit(maxWorkerExecuteTimeUnit); JsonObject json = options.toJson(); DeploymentOptions copy = new DeploymentOptions(json); - assertEquals(worker, copy.isWorker()); assertEquals("bar", copy.getConfig().getString("foo")); assertEquals(ha, copy.isHa()); assertEquals(poolName, copy.getWorkerPoolName()); @@ -231,7 +222,7 @@ public void testDeployFromContext() throws Exception { @Test public void testDeployWorkerFromTestThread() throws Exception { MyVerticle verticle = new MyVerticle(); - vertx.deployVerticle(verticle, new DeploymentOptions().setWorker(true)).onComplete(ar -> { + vertx.deployVerticle(verticle, new DeploymentOptions().setThreadingModel(ThreadingModel.WORKER)).onComplete(ar -> { assertDeployment(1, verticle, null, ar); assertTrue(verticle.startContext.isWorkerContext()); vertx.undeploy(ar.result()).onComplete(ar2 -> { @@ -247,7 +238,7 @@ public void testDeployWorkerFromTestThread() throws Exception { public void testDeployWorkerWithConfig() throws Exception { MyVerticle verticle = new MyVerticle(); JsonObject conf = generateJSONObject(); - vertx.deployVerticle(verticle, new DeploymentOptions().setConfig(conf).setWorker(true)).onComplete(ar -> { + vertx.deployVerticle(verticle, new DeploymentOptions().setConfig(conf).setThreadingModel(ThreadingModel.WORKER)).onComplete(ar -> { assertDeployment(1, verticle, conf, ar); assertTrue(verticle.startContext.isWorkerContext()); assertFalse(verticle.startContext.isEventLoopContext()); @@ -278,7 +269,7 @@ public void stop() throws Exception { assertFalse(Context.isOnEventLoopThread()); } }; - vertx.deployVerticle(verticle, new DeploymentOptions().setWorker(true)) + vertx.deployVerticle(verticle, new DeploymentOptions().setThreadingModel(ThreadingModel.WORKER)) .onComplete(onSuccess(res -> { assertTrue(Context.isOnVertxThread()); assertFalse(Context.isOnWorkerThread()); @@ -1270,13 +1261,13 @@ public void testUndeployParentDuringChildDeployment() throws Exception { MyAsyncVerticle childVerticle = new MyAsyncVerticle(startPromise -> { deployLatch.countDown(); - Vertx.currentContext().executeBlocking(prom -> { + Vertx.currentContext().executeBlocking(() -> { try { undeployLatch.await(); - prom.complete(); + return null; } catch (InterruptedException e) { Thread.currentThread().interrupt(); - prom.fail(e.getMessage()); + throw e; } }).onComplete(startPromise); }, Promise::complete); diff --git a/src/test/java/io/vertx/core/EventLoopGroupTest.java b/src/test/java/io/vertx/core/EventLoopGroupTest.java deleted file mode 100644 index d0682cc1c51..00000000000 --- a/src/test/java/io/vertx/core/EventLoopGroupTest.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core; - -import io.netty.channel.EventLoopGroup; -import io.vertx.test.core.VertxTestBase; -import org.junit.Test; - -/** - * @author Tim Fox - */ -public class EventLoopGroupTest extends VertxTestBase { - - @Test - public void testGetEventLoopGroup() { - EventLoopGroup elp = vertx.nettyEventLoopGroup(); - assertNotNull(elp); - } -} diff --git a/src/test/java/io/vertx/core/ExecuteBlockingTest.java b/src/test/java/io/vertx/core/ExecuteBlockingTest.java index f72f332dfff..07ead9c73f3 100644 --- a/src/test/java/io/vertx/core/ExecuteBlockingTest.java +++ b/src/test/java/io/vertx/core/ExecuteBlockingTest.java @@ -11,10 +11,12 @@ package io.vertx.core; +import io.vertx.core.impl.NoStackTraceException; import io.vertx.test.core.VertxTestBase; import org.junit.Test; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; /** @@ -25,12 +27,12 @@ public class ExecuteBlockingTest extends VertxTestBase { @Test public void testExecuteBlockingSuccess() { - vertx.executeBlocking(future -> { + vertx.executeBlocking(() -> { try { Thread.sleep(1000); } catch (Exception ignore) { } - future.complete("done!"); + return "done!"; }).onComplete(onSuccess(res -> { assertEquals("done!", res); testComplete(); @@ -41,12 +43,12 @@ public void testExecuteBlockingSuccess() { @Test public void testExecuteBlockingFailed() { - vertx.executeBlocking(future -> { + vertx.executeBlocking(() -> { try { Thread.sleep(1000); } catch (Exception ignore) { } - future.fail("failed!"); + throw new NoStackTraceException("failed!"); }).onComplete(onFailure(t -> { assertEquals("failed!", t.getMessage()); testComplete(); @@ -57,7 +59,7 @@ public void testExecuteBlockingFailed() { @Test public void testExecuteBlockingThrowsRTE() { - vertx.executeBlocking(future -> { + vertx.executeBlocking(() -> { throw new RuntimeException("rte"); }).onComplete(onFailure(t -> { assertEquals("rte", t.getMessage()); @@ -72,7 +74,7 @@ public void testExecuteBlockingContext() { vertx.runOnContext(v -> { Context ctx = vertx.getOrCreateContext(); assertTrue(ctx.isEventLoopContext()); - vertx.executeBlocking(future -> { + vertx.executeBlocking(() -> { assertSame(ctx, vertx.getOrCreateContext()); assertTrue(Thread.currentThread().getName().startsWith("vert.x-worker-thread")); assertTrue(Context.isOnWorkerThread()); @@ -81,13 +83,16 @@ public void testExecuteBlockingContext() { Thread.sleep(1000); } catch (Exception ignore) { } + CountDownLatch latch = new CountDownLatch(1); vertx.runOnContext(v2 -> { assertSame(ctx, vertx.getOrCreateContext()); assertTrue(Thread.currentThread().getName().startsWith("vert.x-eventloop-thread")); assertFalse(Context.isOnWorkerThread()); assertTrue(Context.isOnEventLoopThread()); - future.complete("done!"); + latch.countDown(); }); + assertTrue(latch.await(20, TimeUnit.SECONDS)); + return "done!"; }).onComplete(onSuccess(res -> { assertSame(ctx, vertx.getOrCreateContext()); assertTrue(Thread.currentThread().getName().startsWith("vert.x-eventloop-thread")); @@ -107,9 +112,9 @@ public void testExecuteBlockingTTCL() throws Exception { assertNotNull(cl); CountDownLatch latch = new CountDownLatch(1); AtomicReference blockingTCCL = new AtomicReference<>(); - vertx.executeBlocking(future -> { - future.complete("whatever"); + vertx.executeBlocking(() -> { blockingTCCL.set(Thread.currentThread().getContextClassLoader()); + return "whatever"; }).onComplete(onSuccess(res -> { assertEquals("whatever", res); latch.countDown(); @@ -132,7 +137,7 @@ public void testExecuteBlockingParallel() throws Exception { assertTrue(ctx.isEventLoopContext()); for (int i = 0; i < numExecBlocking; i++) { - vertx.executeBlocking(future -> { + vertx.executeBlocking(() -> { assertSame(ctx, vertx.getOrCreateContext()); assertTrue(Thread.currentThread().getName().startsWith("vert.x-worker-thread")); assertTrue(Context.isOnWorkerThread()); @@ -141,7 +146,7 @@ public void testExecuteBlockingParallel() throws Exception { Thread.sleep(pause); } catch (Exception ignore) { } - future.complete("done!"); + return "done!"; }, false).onComplete(onSuccess(res -> { assertSame(ctx, vertx.getOrCreateContext()); assertTrue(Thread.currentThread().getName().startsWith("vert.x-eventloop-thread")); diff --git a/src/test/java/io/vertx/core/FakeContext.java b/src/test/java/io/vertx/core/FakeContext.java index 1d8845bc646..82e19db4cbd 100644 --- a/src/test/java/io/vertx/core/FakeContext.java +++ b/src/test/java/io/vertx/core/FakeContext.java @@ -12,6 +12,7 @@ import io.vertx.core.json.JsonObject; import io.vertx.core.spi.tracing.VertxTracer; +import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; @@ -33,8 +34,8 @@ public Executor executor() { } @Override - public void runOnContext(Handler action) { - + public ThreadingModel threadingModel() { + return null; } @Override @@ -43,7 +44,7 @@ public boolean inThread() { } @Override - public Future<@Nullable T> executeBlocking(Handler> blockingCodeHandler, boolean ordered) { + public Future<@Nullable T> executeBlocking(Callable blockingCodeHandler, boolean ordered) { return null; } @@ -88,17 +89,17 @@ public EventLoop nettyEventLoop() { } @Override - public Future executeBlocking(Handler> blockingCodeHandler, TaskQueue queue) { + public Future executeBlocking(Callable blockingCodeHandler, TaskQueue queue) { return null; } @Override - public Future executeBlockingInternal(Handler> action) { + public Future executeBlockingInternal(Callable action) { return null; } @Override - public Future executeBlockingInternal(Handler> action, boolean ordered) { + public Future executeBlockingInternal(Callable action, boolean ordered) { return null; } diff --git a/src/test/java/io/vertx/core/FutureTest.java b/src/test/java/io/vertx/core/FutureTest.java index cb2e2c33616..fe8c71ed2d7 100644 --- a/src/test/java/io/vertx/core/FutureTest.java +++ b/src/test/java/io/vertx/core/FutureTest.java @@ -22,16 +22,14 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; /** * @author Julien Viet @@ -396,7 +394,7 @@ private void testEventuallySuccessTo(Consumer> op) { Future c = p.future(); Promise p3 = Promise.promise(); Future f3 = p3.future(); - Future f4 = f3.eventually(v -> { + Future f4 = f3.eventually(() -> { cnt.incrementAndGet(); return c; }); @@ -425,7 +423,7 @@ private void testEventuallyFailureTo(Consumer> op) { Future c = p.future(); Promise p3 = Promise.promise(); Future f3 = p3.future(); - Future f4 = f3.eventually(v -> { + Future f4 = f3.eventually(() -> { cnt.incrementAndGet(); return c; }); @@ -823,11 +821,12 @@ public boolean tryFail(Throwable cause) { public boolean failed() { throw new UnsupportedOperationException(); } public Future compose(Function> successMapper, Function> failureMapper) { throw new UnsupportedOperationException(); } public Future transform(Function, Future> mapper) { throw new UnsupportedOperationException(); } - public Future eventually(Function> mapper) { throw new UnsupportedOperationException(); } + public Future eventually(Supplier> mapper) { throw new UnsupportedOperationException(); } public Future map(Function mapper) { throw new UnsupportedOperationException(); } public Future map(V value) { throw new UnsupportedOperationException(); } public Future otherwise(Function mapper) { throw new UnsupportedOperationException(); } public Future otherwise(T value) { throw new UnsupportedOperationException(); } + public Future timeout(long delay, TimeUnit unit) { throw new UnsupportedOperationException(); } public void handle(AsyncResult asyncResult) { if (asyncResult.succeeded()) { @@ -1303,13 +1302,20 @@ public void testSeveralHandlers3() { @Test public void testSuccessNotification() { - waitFor(2); + waitFor(3); Promise promise = Promise.promise(); Future fut = promise.future(); fut.onComplete(onSuccess(res -> { assertEquals("foo", res); complete(); })); + fut.onComplete( + res -> { + assertEquals("foo", res); + complete(); + }, + err -> fail() + ); fut.onSuccess(res -> { assertEquals("foo", res); complete(); @@ -1323,7 +1329,7 @@ public void testSuccessNotification() { @Test public void testFailureNotification() { - waitFor(2); + waitFor(3); Promise promise = Promise.promise(); Future fut = promise.future(); Throwable failure = new Throwable(); @@ -1331,6 +1337,13 @@ public void testFailureNotification() { assertEquals(failure, err); complete(); })); + fut.onComplete( + res -> fail(), + err -> { + assertEquals(failure, err); + complete(); + } + ); fut.onSuccess(res -> { fail(); }); @@ -1714,4 +1727,87 @@ public void testAndThenCompleteHandlerWithError() { await(); } + + @Test + public void testAwaitFromPlainThread() { + try { + Promise.promise().future().await(); + fail(); + } catch (IllegalStateException e) { + } + } + + @Test + public void contextFutureTimeoutFires() { + ContextInternal ctx = (ContextInternal) vertx.getOrCreateContext(); + Promise promise = ctx.promise(); + Future fut = promise.future(); + futureTimeoutFires(ctx, fut); + } + + @Test + public void futureTimeoutFires() { + disableThreadChecks(); + Promise promise = Promise.promise(); + Future fut = promise.future(); + futureTimeoutFires(null, fut); + } + + private void futureTimeoutFires(Context ctx, Future fut) { + Future timeout = fut.timeout(100, TimeUnit.MILLISECONDS); + timeout.onComplete(onFailure(err -> { + assertTrue(err instanceof TimeoutException); + assertSame(Vertx.currentContext(), ctx); + testComplete(); + })); + await(); + } + + @Test + public void contextFutureTimeoutExpires() throws Exception { + ContextInternal ctx = (ContextInternal) vertx.getOrCreateContext(); + Promise promise = ctx.promise(); + futureTimeoutExpires(ctx, promise); + } + + @Test + public void futureTimeoutExpires() throws Exception { + disableThreadChecks(); + Promise promise = Promise.promise(); + futureTimeoutExpires(null, promise); + } + + private void futureTimeoutExpires(Context ctx, Promise promise) throws Exception { + Future timeout = promise.future().timeout(10, TimeUnit.SECONDS); + timeout.onComplete(onSuccess(val -> { + assertSame(Vertx.currentContext(), ctx); + assertEquals("value", val); + testComplete(); + })); + Thread.sleep(100); + promise.complete("value"); + await(); + } + + @Test + public void contextCompletedFutureTimeout() throws Exception { + ContextInternal ctx = (ContextInternal) vertx.getOrCreateContext(); + completedFutureTimeout(ctx, ctx.succeededFuture("value")); + } + + @Test + public void completedFutureTimeout() throws Exception { + disableThreadChecks(); + completedFutureTimeout(null, Future.succeededFuture("value")); + } + + private void completedFutureTimeout(Context ctx, Future future) throws Exception { + Future timeout = future.timeout(10, TimeUnit.SECONDS); + timeout.onComplete(onSuccess(val -> { + assertSame(Vertx.currentContext(), ctx); + assertEquals("value", val); + testComplete(); + })); + await(); + } } diff --git a/src/test/java/io/vertx/core/HATest.java b/src/test/java/io/vertx/core/HATest.java index 9fb700215ca..4d5503f8879 100644 --- a/src/test/java/io/vertx/core/HATest.java +++ b/src/test/java/io/vertx/core/HATest.java @@ -362,11 +362,8 @@ protected Vertx startVertx(String haGroup, int quorumSize) throws Exception { } protected Vertx startVertx(String haGroup, int quorumSize, boolean ha) throws Exception { - VertxOptions options = new VertxOptions() - .setHAEnabled(ha) - .setClusterManager(getClusterManager()); - options.getEventBusOptions() - .setHost("localhost"); + VertxOptions options = new VertxOptions().setHAEnabled(ha); + options.getEventBusOptions().setHost("localhost"); if (ha) { options.setQuorumSize(quorumSize); if (haGroup != null) { @@ -375,10 +372,14 @@ protected Vertx startVertx(String haGroup, int quorumSize, boolean ha) throws Ex } CountDownLatch latch = new CountDownLatch(1); AtomicReference vertxRef = new AtomicReference<>(); - clusteredVertx(options, onSuccess(vertx -> { - vertxRef.set(vertx); - latch.countDown(); - })); + Vertx.builder() + .with(options) + .withClusterManager(getClusterManager()) + .buildClustered() + .onComplete(onSuccess(vertx -> { + vertxRef.set(vertx); + latch.countDown(); + })); latch.await(2, TimeUnit.MINUTES); return vertxRef.get(); } @@ -397,13 +398,9 @@ protected void checkDeploymentExists(int pos, String verticleName, DeploymentOpt protected void kill(int pos) { VertxInternal v = (VertxInternal)vertices[pos]; - v.executeBlocking(fut -> { - try { - v.simulateKill(); - fut.complete(); - } catch (Exception e) { - fut.fail(e); - } + v.executeBlocking(() -> { + v.simulateKill(); + return null; }, false).onComplete(onSuccess(ar -> { })); } diff --git a/src/test/java/io/vertx/core/IsolatingClassLoaderTest.java b/src/test/java/io/vertx/core/IsolatingClassLoaderTest.java deleted file mode 100644 index e73d7a7c915..00000000000 --- a/src/test/java/io/vertx/core/IsolatingClassLoaderTest.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core; - -import io.vertx.core.impl.IsolatingClassLoader; -import io.vertx.core.json.JsonObject; -import org.junit.Before; -import org.junit.Test; - -import java.io.File; -import java.io.InputStream; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Collections; -import java.util.Enumeration; -import java.util.List; -import java.util.Scanner; - -import static org.junit.Assert.*; - -/** - * Unit tests for {@link io.vertx.core.impl.IsolatingClassLoader} - */ -public class IsolatingClassLoaderTest { - - private String resourceName = "resource.json"; - private URL url1; - private URL url2; - private URL url3; - private URLClassLoader ucl; - private IsolatingClassLoader icl; - - @Before - public void setUp() throws Exception { - - String basePath = "src/test/resources/icl"; - - url1 = new File(basePath, "pkg1").toURI().toURL(); - url2 = new File(basePath, "pkg2").toURI().toURL(); - url3 = new File(basePath, "pkg3").toURI().toURL(); - - ucl = new URLClassLoader(new URL[]{url2, url3}); - icl = new IsolatingClassLoader(new URL[]{url1}, ucl, null); - - } - - @Test - public void testGetResource() throws Exception { - - URL resource = ucl.getResource(resourceName); - checkResource(url2, resource); - - resource = icl.getResource(resourceName); - checkResource(url1, resource); - - } - - @Test - public void testGetResourceNull() throws Exception { - - resourceName = "null_resource"; - URL resource = ucl.getResource(resourceName); - assertNull(resource); - - resource = icl.getResource(resourceName); - assertNull(resource); - - } - - @Test - public void testGetResources() throws Exception { - - Enumeration resources = ucl.getResources(resourceName); - List list = Collections.list(resources); - assertEquals(2, list.size()); - checkResource(url2, list.get(0)); - checkResource(url3, list.get(1)); - - resources = icl.getResources(resourceName); - list = Collections.list(resources); - assertEquals(3, list.size()); - checkResource(url1, list.get(0)); - checkResource(url2, list.get(1)); - checkResource(url3, list.get(2)); - - } - - private void checkResource(URL expected, URL resource) throws Exception { - assertEquals(expected.toString() + resourceName, resource.toString()); - } - - @Test - public void testGetResourcesNull() throws Exception { - - resourceName = "null_resource"; - Enumeration resources = ucl.getResources(resourceName); - List list = Collections.list(resources); - assertEquals(0, list.size()); - - resources = icl.getResources(resourceName); - list = Collections.list(resources); - assertEquals(0, list.size()); - - } - - @Test - public void testGetResourceAsStream() throws Exception { - - testGetResourceAsStream(2, ucl); - testGetResourceAsStream(1, icl); - - } - - private void testGetResourceAsStream(long ver, ClassLoader cl) throws Exception { - - try (InputStream is = cl.getResourceAsStream(resourceName)) { - assertNotNull(is); - - try (Scanner scanner = new Scanner(is, "UTF-8").useDelimiter("\\A")) { - assertTrue(scanner.hasNext()); - JsonObject json = new JsonObject(scanner.next()); - assertEquals(ver, json.getLong("ver", -1L).longValue()); - } - } - - } - -} diff --git a/src/test/java/io/vertx/core/LauncherTest.java b/src/test/java/io/vertx/core/LauncherTest.java deleted file mode 100644 index 77e640ae2df..00000000000 --- a/src/test/java/io/vertx/core/LauncherTest.java +++ /dev/null @@ -1,790 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core; - -import io.vertx.core.impl.launcher.commands.HelloCommand; -import io.vertx.core.impl.launcher.commands.RunCommand; -import io.vertx.core.impl.launcher.commands.VersionCommand; -import io.vertx.core.json.JsonObject; -import io.vertx.core.spi.metrics.MetricsOptionsTest; -import io.vertx.test.core.TestUtils; -import io.vertx.test.core.VertxTestBase; -import io.vertx.test.verticles.TestVerticle; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.io.*; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.Arrays; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -public class LauncherTest extends VertxTestBase { - - private String expectedVersion; - private ByteArrayOutputStream out; - private PrintStream stream; - private Vertx vertx; - - @Override - public void setUp() throws Exception { - super.setUp(); - TestVerticle.instanceCount.set(0); - TestVerticle.processArgs = null; - TestVerticle.conf = null; - - // Read the expected version from the vertx=version.txt - final URL resource = this.getClass().getClassLoader().getResource("META-INF/vertx/vertx-version.txt"); - if (resource == null) { - throw new IllegalStateException("Cannot find the vertx-version.txt"); - } else { - try (BufferedReader in = new BufferedReader(new InputStreamReader(resource.openStream()))) { - expectedVersion = in.readLine(); - } - } - - Launcher.resetProcessArguments(); - - out = new ByteArrayOutputStream(); - stream = new PrintStream(out); - } - - @Override - public void tearDown() throws Exception { - clearProperties(); - super.tearDown(); - - out.close(); - stream.close(); - - if (vertx != null) { - vertx.close(); - } - } - - - @Test - public void testVersion() throws Exception { - String[] args = {"-version"}; - MyLauncher launcher = new MyLauncher(); - - launcher.dispatch(args); - - final VersionCommand version = (VersionCommand) launcher.getExistingCommandInstance("version"); - assertNotNull(version); - assertEquals(version.getVersion(), expectedVersion); - } - - @Test - public void testRunVerticleWithoutArgs() throws Exception { - MyLauncher launcher = new MyLauncher(); - String[] args = {"run", "java:" + TestVerticle.class.getCanonicalName()}; - launcher.dispatch(args); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 1); - assertEquals(Arrays.asList(args), TestVerticle.processArgs); - launcher.assertHooksInvoked(); - } - - @Test - public void testRunWithoutArgs() throws Exception { - MyLauncher launcher = new MyLauncher() { - @Override - public PrintStream getPrintStream() { - return stream; - } - }; - String[] args = {"run"}; - launcher.dispatch(args); - assertTrue(out.toString().contains("The argument 'main-verticle' is required")); - } - - @Test - public void testNoArgsAndNoMainVerticle() throws Exception { - MyLauncher launcher = new MyLauncher() { - @Override - public PrintStream getPrintStream() { - return stream; - } - }; - String[] args = {}; - launcher.dispatch(args); - assertTrue(out.toString().contains("Usage:")); - assertTrue(out.toString().contains("bare")); - assertTrue(out.toString().contains("run")); - assertTrue(out.toString().contains("hello")); - } - - @Test - public void testRunVerticle() throws Exception { - testRunVerticleMultiple(1); - } - - @Test - public void testRunVerticleMultipleInstances() throws Exception { - testRunVerticleMultiple(10); - } - - public void testRunVerticleMultiple(int instances) throws Exception { - MyLauncher launcher = new MyLauncher(); - String[] args = {"run", "java:" + TestVerticle.class.getCanonicalName(), "-instances", String.valueOf(instances)}; - launcher.dispatch(args); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == instances); - assertEquals(Arrays.asList(args), TestVerticle.processArgs); - launcher.assertHooksInvoked(); - } - - @Test - public void testRunVerticleClustered() throws Exception { - MyLauncher launcher = new MyLauncher(); - String[] args = {"run", "java:" + TestVerticle.class.getCanonicalName(), "-cluster"}; - launcher.dispatch(args); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 1); - assertEquals(Arrays.asList(args), TestVerticle.processArgs); - launcher.assertHooksInvoked(); - } - - @Test - public void testRunVerticleHA() throws Exception { - MyLauncher launcher = new MyLauncher(); - String[] args = {"run", "java:" + TestVerticle.class.getCanonicalName(), "-ha"}; - launcher.dispatch(args); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 1); - assertEquals(Arrays.asList(args), TestVerticle.processArgs); - launcher.assertHooksInvoked(); - } - - @Test - public void testRunVerticleWithMainVerticleInManifestNoArgs() throws Exception { - // Copy the right manifest - File manifest = new File("target/test-classes/META-INF/MANIFEST-Launcher.MF"); - if (!manifest.isFile()) { - throw new IllegalStateException("Cannot find the MANIFEST-Launcher.MF file"); - } - File target = new File("target/test-classes/META-INF/MANIFEST.MF"); - Files.copy(manifest.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); - - Launcher launcher = new Launcher(); - String[] args = new String[0]; - launcher.dispatch(args); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 1); - assertEquals(Arrays.asList(args), TestVerticle.processArgs); - - cleanup(launcher); - } - - private void cleanup(Launcher launcher) { - RunCommand run = (RunCommand) launcher.getExistingCommandInstance("run"); - if (run != null) { - Vertx v = run.vertx(); - if (v != null) { - v.close(); - } - } - } - - @Test - public void testRunVerticleWithMainVerticleInManifestWithArgs() throws Exception { - // Copy the right manifest - File manifest = new File("target/test-classes/META-INF/MANIFEST-Launcher.MF"); - if (!manifest.isFile()) { - throw new IllegalStateException("Cannot find the MANIFEST-Launcher.MF file"); - } - File target = new File("target/test-classes/META-INF/MANIFEST.MF"); - Files.copy(manifest.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); - - Launcher launcher = new Launcher(); - String[] args = {"-cluster", "-worker", "-instances=10"}; - launcher.dispatch(args); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 10); - assertEquals(Arrays.asList(args), TestVerticle.processArgs); - - cleanup(launcher); - } - - @Test - public void testRunVerticleWithMainVerticleInManifestWithCustomCommand() throws Exception { - // Copy the right manifest - File manifest = new File("target/test-classes/META-INF/MANIFEST-Launcher-hello.MF"); - if (!manifest.isFile()) { - throw new IllegalStateException("Cannot find the MANIFEST-Launcher-hello.MF file"); - } - File target = new File("target/test-classes/META-INF/MANIFEST.MF"); - Files.copy(manifest.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); - - Launcher launcher = new Launcher(); - HelloCommand.called = false; - String[] args = {"--name=vert.x"}; - launcher.dispatch(args); - assertWaitUntil(() -> HelloCommand.called); - } - - @Test - public void testRunVerticleWithoutMainVerticleInManifestButWithCustomCommand() throws Exception { - // Copy the right manifest - File manifest = new File("target/test-classes/META-INF/MANIFEST-Launcher-Default-Command.MF"); - if (!manifest.isFile()) { - throw new IllegalStateException("Cannot find the MANIFEST-Default-Command.MF file"); - } - File target = new File("target/test-classes/META-INF/MANIFEST.MF"); - Files.copy(manifest.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); - - Launcher launcher = new Launcher(); - HelloCommand.called = false; - String[] args = {"--name=vert.x"}; - launcher.dispatch(args); - assertWaitUntil(() -> HelloCommand.called); - } - - @Test - public void testRunWithOverriddenDefaultCommand() throws Exception { - // Copy the right manifest - File manifest = new File("target/test-classes/META-INF/MANIFEST-Launcher-hello.MF"); - if (!manifest.isFile()) { - throw new IllegalStateException("Cannot find the MANIFEST-Launcher-hello.MF file"); - } - File target = new File("target/test-classes/META-INF/MANIFEST.MF"); - Files.copy(manifest.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); - - HelloCommand.called = false; - String[] args = {"run", TestVerticle.class.getName(), "--name=vert.x"}; - Launcher launcher = new Launcher(); - launcher.dispatch(args); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 1); - - cleanup(launcher); - } - - @Test - public void testRunWithOverriddenDefaultCommandRequiringArgs() throws Exception { - // Copy the right manifest - File manifest = new File("target/test-classes/META-INF/MANIFEST-Launcher-run.MF"); - if (!manifest.isFile()) { - throw new IllegalStateException("Cannot find the MANIFEST-Launcher-run.MF file"); - } - File target = new File("target/test-classes/META-INF/MANIFEST.MF"); - Files.copy(manifest.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); - - String[] args = {TestVerticle.class.getName()}; - Launcher launcher = new Launcher(); - launcher.dispatch(args); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 1); - - cleanup(launcher); - } - - - @Test - public void testRunVerticleWithExtendedMainVerticleNoArgs() throws Exception { - MySecondLauncher launcher = new MySecondLauncher(); - String[] args = new String[0]; - launcher.dispatch(args); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 1); - assertEquals(Arrays.asList(args), TestVerticle.processArgs); - } - - @Test - public void testRunVerticleWithExtendedMainVerticleWithArgs() throws Exception { - MySecondLauncher launcher = new MySecondLauncher(); - String[] args = {"-cluster", "-worker"}; - launcher.dispatch(args); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 1); - assertEquals(Arrays.asList(args), TestVerticle.processArgs); - } - - @Test - public void testFatJarWithHelp() throws Exception { - // Copy the right manifest - File manifest = new File("target/test-classes/META-INF/MANIFEST-Launcher.MF"); - if (!manifest.isFile()) { - throw new IllegalStateException("Cannot find the MANIFEST-Launcher.MF file"); - } - File target = new File("target/test-classes/META-INF/MANIFEST.MF"); - Files.copy(manifest.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); - - Launcher launcher = new Launcher() { - @Override - public PrintStream getPrintStream() { - return stream; - } - }; - - String[] args = {"--help"}; - launcher.dispatch(args); - assertTrue(out.toString().contains("Usage")); - assertTrue(out.toString().contains("run")); - assertTrue(out.toString().contains("version")); - assertTrue(out.toString().contains("bare")); - } - - @Test - public void testFatJarWithCommandHelp() throws Exception { - // Copy the right manifest - File manifest = new File("target/test-classes/META-INF/MANIFEST-Launcher.MF"); - if (!manifest.isFile()) { - throw new IllegalStateException("Cannot find the MANIFEST-Launcher.MF file"); - } - File target = new File("target/test-classes/META-INF/MANIFEST.MF"); - Files.copy(manifest.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); - - Launcher launcher = new Launcher() { - @Override - public PrintStream getPrintStream() { - return stream; - } - }; - String[] args = {"hello", "--help"}; - launcher.dispatch(args); - assertTrue(out.toString().contains("Usage")); - assertTrue(out.toString().contains("hello")); - assertTrue(out.toString().contains("A simple command to wish you a good day.")); // Description text. - } - - @Test - public void testFatJarWithMissingCommandHelp() throws Exception { - // Copy the right manifest - File manifest = new File("target/test-classes/META-INF/MANIFEST-Launcher.MF"); - if (!manifest.isFile()) { - throw new IllegalStateException("Cannot find the MANIFEST-Launcher.MF file"); - } - File target = new File("target/test-classes/META-INF/MANIFEST.MF"); - Files.copy(manifest.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); - - Launcher launcher = new Launcher() { - @Override - public PrintStream getPrintStream() { - return stream; - } - }; - String[] args = {"not-a-command", "--help"}; - launcher.dispatch(args); - assertTrue(out.toString().contains("The command 'not-a-command' is not a valid command.")); - } - - @Test - public void testRunVerticleWithConfString() throws Exception { - MyLauncher launcher = new MyLauncher(); - JsonObject conf = new JsonObject().put("foo", "bar").put("wibble", 123); - String[] args = {"run", "java:" + TestVerticle.class.getCanonicalName(), "-conf", conf.encode()}; - launcher.dispatch(args); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 1); - assertEquals(conf, TestVerticle.conf); - } - - @Rule - public TemporaryFolder testFolder = new TemporaryFolder(); - - - @Test - public void testRunVerticleWithConfFile() throws Exception { - Path tempDir = testFolder.newFolder().toPath(); - Path tempFile = Files.createTempFile(tempDir, "conf", "json"); - MyLauncher launcher = new MyLauncher(); - JsonObject conf = new JsonObject().put("foo", "bar").put("wibble", 123); - Files.write(tempFile, conf.encode().getBytes()); - String[] args = {"run", "java:" + TestVerticle.class.getCanonicalName(), "-conf", tempFile.toString()}; - launcher.dispatch(args); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 1); - assertEquals(conf, TestVerticle.conf); - } - - @Test - public void testConfigureFromSystemProperties() throws Exception { - testConfigureFromSystemProperties(false); - } - - @Test - public void testConfigureFromSystemPropertiesClustered() throws Exception { - testConfigureFromSystemProperties(true); - } - - private void testConfigureFromSystemProperties(boolean clustered) throws Exception { - - // One for each type that we support - System.setProperty(RunCommand.VERTX_OPTIONS_PROP_PREFIX + "eventLoopPoolSize", "123"); - System.setProperty(RunCommand.VERTX_OPTIONS_PROP_PREFIX + "maxEventLoopExecuteTime", "123767667"); - System.setProperty(RunCommand.METRICS_OPTIONS_PROP_PREFIX + "enabled", "true"); - System.setProperty(RunCommand.VERTX_OPTIONS_PROP_PREFIX + "haGroup", "somegroup"); - System.setProperty(RunCommand.VERTX_OPTIONS_PROP_PREFIX + "maxEventLoopExecuteTimeUnit", "SECONDS"); - - MyLauncher launcher = new MyLauncher(); - String[] args; - if (clustered) { - args = new String[]{"run", "java:" + TestVerticle.class.getCanonicalName(), "-cluster"}; - } else { - args = new String[]{"run", "java:" + TestVerticle.class.getCanonicalName()}; - } - launcher.dispatch(args); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 1); - - VertxOptions opts = launcher.getVertxOptions(); - - assertEquals(123, opts.getEventLoopPoolSize(), 0); - assertEquals(123767667L, opts.getMaxEventLoopExecuteTime()); - assertEquals(true, opts.getMetricsOptions().isEnabled()); - assertEquals("somegroup", opts.getHAGroup()); - assertEquals(TimeUnit.SECONDS, opts.getMaxEventLoopExecuteTimeUnit()); - } - - private void clearProperties() { - Set toClear = new HashSet<>(); - Enumeration e = System.getProperties().propertyNames(); - // Uhh, properties suck - while (e.hasMoreElements()) { - String propName = (String) e.nextElement(); - if (propName.startsWith("vertx.options")) { - toClear.add(propName); - } - } - toClear.forEach(System::clearProperty); - } - - @Test - public void testConfigureFromJsonFile() throws Exception { - testConfigureFromJson(true); - } - - @Test - public void testConfigureFromJsonString() throws Exception { - testConfigureFromJson(false); - } - - private void testConfigureFromJson(boolean jsonFile) throws Exception { - JsonObject json = new JsonObject() - .put("eventLoopPoolSize", 123) - .put("maxEventLoopExecuteTime", 123767667) - .put("metricsOptions", new JsonObject().put("enabled", true)) - .put("eventBusOptions", new JsonObject().put("clusterPublicHost", "mars")) - .put("haGroup", "somegroup") - .put("maxEventLoopExecuteTimeUnit", "SECONDS"); - - String optionsArg; - if (jsonFile) { - File file = testFolder.newFile(); - Files.write(file.toPath(), json.toBuffer().getBytes()); - optionsArg = file.getPath(); - } else { - optionsArg = json.toString(); - } - - MyLauncher launcher = new MyLauncher(); - String[] args = new String[]{"run", "java:" + TestVerticle.class.getCanonicalName(), "-options", optionsArg}; - launcher.dispatch(args); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 1); - - VertxOptions opts = launcher.getVertxOptions(); - - assertEquals(123, opts.getEventLoopPoolSize(), 0); - assertEquals(123767667L, opts.getMaxEventLoopExecuteTime()); - assertEquals(true, opts.getMetricsOptions().isEnabled()); - assertEquals("mars", opts.getEventBusOptions().getClusterPublicHost()); - assertEquals("somegroup", opts.getHAGroup()); - assertEquals(TimeUnit.SECONDS, opts.getMaxEventLoopExecuteTimeUnit()); - } - - @Test - public void testCustomMetricsOptions() throws Exception { - System.setProperty(RunCommand.METRICS_OPTIONS_PROP_PREFIX + "enabled", "true"); - System.setProperty(RunCommand.METRICS_OPTIONS_PROP_PREFIX + "customProperty", "customPropertyValue"); - MyLauncher launcher = new MyLauncher(); - String[] args = {"run", "java:" + TestVerticle.class.getCanonicalName()}; - ClassLoader oldCL = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader(MetricsOptionsTest.createMetricsFromMetaInfLoader("io.vertx.core.CustomMetricsFactory")); - try { - launcher.dispatch(args); - } finally { - Thread.currentThread().setContextClassLoader(oldCL); - } - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 1); - VertxOptions opts = launcher.getVertxOptions(); - CustomMetricsOptions custom = (CustomMetricsOptions) opts.getMetricsOptions(); - assertEquals("customPropertyValue", custom.getCustomProperty()); - assertTrue(launcher.getVertx().isMetricsEnabled()); - } - - @Test - public void testConfigureFromSystemPropertiesInvalidPropertyName() throws Exception { - - System.setProperty(RunCommand.VERTX_OPTIONS_PROP_PREFIX + "nosuchproperty", "123"); - - // Should be ignored - - MyLauncher launcher = new MyLauncher(); - String[] args = {"run", "java:" + TestVerticle.class.getCanonicalName()}; - launcher.dispatch(args); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 1); - - VertxOptions opts = launcher.getVertxOptions(); - VertxOptions def = new VertxOptions(); - if (opts.getMetricsOptions().isEnabled()) { - def.getMetricsOptions().setEnabled(true); - } - assertEquals(def.toJson(), opts.toJson()); - - } - - @Test - public void testConfigureFromSystemPropertiesInvalidPropertyType() throws Exception { - // One for each type that we support - System.setProperty(RunCommand.VERTX_OPTIONS_PROP_PREFIX + "eventLoopPoolSize", "sausages"); - // Should be ignored - - MyLauncher launcher = new MyLauncher(); - String[] args = {"run", "java:" + TestVerticle.class.getCanonicalName()}; - launcher.dispatch(args); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 1); - - VertxOptions opts = launcher.getVertxOptions(); - VertxOptions def = new VertxOptions(); - if (opts.getMetricsOptions().isEnabled()) { - def.getMetricsOptions().setEnabled(true); - } - assertEquals(def.toJson(), opts.toJson()); - } - - @Test - public void testCustomMetricsOptionsFromJsonFile() throws Exception { - testCustomMetricsOptionsFromJson(true); - } - - @Test - public void testCustomMetricsOptionsFromJsonString() throws Exception { - testCustomMetricsOptionsFromJson(false); - } - - private void testCustomMetricsOptionsFromJson(boolean jsonFile) throws Exception { - JsonObject json = new JsonObject() - .put("metricsOptions", new JsonObject() - .put("enabled", true) - .put("customProperty", "customPropertyValue") - .put("nestedOptions", new JsonObject().put("nestedProperty", "nestedValue"))); - - String optionsArg; - if (jsonFile) { - File file = testFolder.newFile(); - Files.write(file.toPath(), json.toBuffer().getBytes()); - optionsArg = file.getPath(); - } else { - optionsArg = json.toString(); - } - - MyLauncher launcher = new MyLauncher(); - String[] args = {"run", "java:" + TestVerticle.class.getCanonicalName(), "-options", optionsArg}; - ClassLoader oldCL = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader(MetricsOptionsTest.createMetricsFromMetaInfLoader("io.vertx.core.CustomMetricsFactory")); - try { - launcher.dispatch(args); - } finally { - Thread.currentThread().setContextClassLoader(oldCL); - } - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 1); - - VertxOptions opts = launcher.getVertxOptions(); - CustomMetricsOptions custom = (CustomMetricsOptions) opts.getMetricsOptions(); - assertEquals("customPropertyValue", custom.getCustomProperty()); - assertEquals("nestedValue", custom.getNestedOptions().getNestedProperty()); - } - - @Test - public void testWhenPassingTheMainObject() throws Exception { - MyLauncher launcher = new MyLauncher(); - int instances = 10; - launcher.dispatch(launcher, new String[]{"run", "java:" + TestVerticle.class.getCanonicalName(), - "-instances", "10"}); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == instances); - } - - @Test - public void testBare() throws Exception { - MyLauncher launcher = new MyLauncher(); - launcher.dispatch(new String[]{"bare"}); - assertWaitUntil(() -> launcher.afterStartingVertxInvoked); - } - - @Test - public void testBareAlias() throws Exception { - MyLauncher launcher = new MyLauncher(); - launcher.dispatch(new String[]{"-ha"}); - assertWaitUntil(() -> launcher.afterStartingVertxInvoked); - } - - @Test - public void testConfigureClusterHostPortFromProperties() throws Exception { - int clusterPort = TestUtils.randomHighPortInt(); - System.setProperty(RunCommand.VERTX_EVENTBUS_PROP_PREFIX + "host", "127.0.0.1"); - System.setProperty(RunCommand.VERTX_EVENTBUS_PROP_PREFIX + "port", Integer.toString(clusterPort)); - MyLauncher launcher = new MyLauncher(); - String[] args = {"run", "java:" + TestVerticle.class.getCanonicalName(), "-cluster"}; - launcher.dispatch(args); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 1); - assertEquals("127.0.0.1", launcher.options.getEventBusOptions().getHost()); - assertEquals(clusterPort, launcher.options.getEventBusOptions().getPort()); - assertNull(launcher.options.getEventBusOptions().getClusterPublicHost()); - assertEquals(-1, launcher.options.getEventBusOptions().getClusterPublicPort()); - } - - @Test - public void testConfigureClusterHostPortFromCommandLine() throws Exception { - int clusterPort = TestUtils.randomHighPortInt(); - MyLauncher launcher = new MyLauncher(); - String[] args = {"run", "java:" + TestVerticle.class.getCanonicalName(), "-cluster", "--cluster-host", "127.0.0.1", "--cluster-port", Integer.toString(clusterPort)}; - launcher.dispatch(args); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 1); - assertEquals("127.0.0.1", launcher.options.getEventBusOptions().getHost()); - assertEquals(clusterPort, launcher.options.getEventBusOptions().getPort()); - assertNull(launcher.options.getEventBusOptions().getClusterPublicHost()); - assertEquals(-1, launcher.options.getEventBusOptions().getClusterPublicPort()); - } - - @Test - public void testConfigureClusterPublicHostPortFromCommandLine() throws Exception { - int clusterPublicPort = TestUtils.randomHighPortInt(); - MyLauncher launcher = new MyLauncher(); - String[] args = {"run", "java:" + TestVerticle.class.getCanonicalName(), "-cluster", "--cluster-public-host", "127.0.0.1", "--cluster-public-port", Integer.toString(clusterPublicPort)}; - launcher.dispatch(args); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 1); - assertEquals("127.0.0.1", launcher.options.getEventBusOptions().getClusterPublicHost()); - assertEquals(clusterPublicPort, launcher.options.getEventBusOptions().getClusterPublicPort()); - } - - @Test - public void testOverrideClusterHostPortFromProperties() throws Exception { - int clusterPort = TestUtils.randomHighPortInt(); - int newClusterPort = TestUtils.randomHighPortInt(); - int newClusterPublicPort = TestUtils.randomHighPortInt(); - System.setProperty(RunCommand.VERTX_OPTIONS_PROP_PREFIX + "clusterHost", "127.0.0.2"); - System.setProperty(RunCommand.VERTX_OPTIONS_PROP_PREFIX + "clusterPort", Integer.toString(clusterPort)); - MyLauncher launcher = new MyLauncher(); - launcher.clusterHost = "127.0.0.1"; - launcher.clusterPort = newClusterPort; - launcher.clusterPublicHost = "127.0.0.3"; - launcher.clusterPublicPort = newClusterPublicPort; - String[] args = {"run", "java:" + TestVerticle.class.getCanonicalName(), "-cluster"}; - launcher.dispatch(args); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 1); - assertEquals("127.0.0.1", launcher.options.getEventBusOptions().getHost()); - assertEquals(newClusterPort, launcher.options.getEventBusOptions().getPort()); - assertEquals("127.0.0.3", launcher.options.getEventBusOptions().getClusterPublicHost()); - assertEquals(newClusterPublicPort, launcher.options.getEventBusOptions().getClusterPublicPort()); - } - - @Test - public void testOverrideClusterHostPortFromCommandLine() throws Exception { - int clusterPort = TestUtils.randomHighPortInt(); - int clusterPublicPort = TestUtils.randomHighPortInt(); - int newClusterPort = TestUtils.randomHighPortInt(); - int newClusterPublicPort = TestUtils.randomHighPortInt(); - MyLauncher launcher = new MyLauncher(); - launcher.clusterHost = "127.0.0.1"; - launcher.clusterPort = newClusterPort; - launcher.clusterPublicHost = "127.0.0.3"; - launcher.clusterPublicPort = newClusterPublicPort; - String[] args = { - "run", "java:" + TestVerticle.class.getCanonicalName(), - "-cluster", - "--cluster-host", "127.0.0.2", "--cluster-port", Integer.toString(clusterPort), - "--cluster-public-host", "127.0.0.4", "--cluster-public-port", Integer.toString(clusterPublicPort) - }; - launcher.dispatch(args); - assertWaitUntil(() -> TestVerticle.instanceCount.get() == 1); - assertEquals("127.0.0.1", launcher.options.getEventBusOptions().getHost()); - assertEquals(newClusterPort, launcher.options.getEventBusOptions().getPort()); - assertEquals("127.0.0.3", launcher.options.getEventBusOptions().getClusterPublicHost()); - assertEquals(newClusterPublicPort, launcher.options.getEventBusOptions().getClusterPublicPort()); - } - - class MyLauncher extends Launcher { - boolean afterConfigParsed = false; - boolean beforeStartingVertxInvoked = false; - boolean afterStartingVertxInvoked = false; - boolean beforeDeployingVerticle = false; - - VertxOptions options; - DeploymentOptions deploymentOptions; - JsonObject config; - String clusterHost; - int clusterPort; - String clusterPublicHost; - int clusterPublicPort; - - PrintStream stream = new PrintStream(out); - - /** - * @return the printer used to write the messages. Defaults to {@link System#out}. - */ - @Override - public PrintStream getPrintStream() { - return stream; - } - - public Vertx getVertx() { - return vertx; - } - - public VertxOptions getVertxOptions() { - return options; - } - - @Override - public void afterConfigParsed(JsonObject config) { - afterConfigParsed = true; - this.config = config; - } - - @Override - public void beforeStartingVertx(VertxOptions options) { - beforeStartingVertxInvoked = true; - this.options = options; - if (clusterHost != null) { - options.getEventBusOptions() - .setHost(clusterHost) - .setPort(clusterPort) - .setClusterPublicHost(clusterPublicHost) - .setClusterPublicPort(clusterPublicPort); - super.beforeStartingVertx(options); - } - } - - @Override - public void afterStartingVertx(Vertx vertx) { - afterStartingVertxInvoked = true; - LauncherTest.this.vertx = vertx; - } - - @Override - public void beforeDeployingVerticle(DeploymentOptions deploymentOptions) { - beforeDeployingVerticle = true; - this.deploymentOptions = deploymentOptions; - } - - public void assertHooksInvoked() { - assertTrue(afterConfigParsed); - assertTrue(beforeStartingVertxInvoked); - assertTrue(afterStartingVertxInvoked); - assertTrue(beforeDeployingVerticle); - assertNotNull(vertx); - } - } - - class MySecondLauncher extends MyLauncher { - - @Override - public String getMainVerticle() { - return "java:io.vertx.test.verticles.TestVerticle"; - } - } -} diff --git a/src/test/java/io/vertx/core/NamedWorkerPoolTest.java b/src/test/java/io/vertx/core/NamedWorkerPoolTest.java index 6603e3e2653..577fc188e90 100644 --- a/src/test/java/io/vertx/core/NamedWorkerPoolTest.java +++ b/src/test/java/io/vertx/core/NamedWorkerPoolTest.java @@ -22,6 +22,7 @@ import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import static java.util.concurrent.TimeUnit.*; @@ -39,12 +40,12 @@ public void testThread() { AtomicBoolean onWorkerThread = new AtomicBoolean(); AtomicBoolean onEventLoopThread = new AtomicBoolean(); AtomicReference threadName = new AtomicReference<>(); - worker.executeBlocking(fut -> { + worker.executeBlocking(() -> { onVertxThread.set(Context.isOnVertxThread()); onWorkerThread.set(Context.isOnWorkerThread()); onEventLoopThread.set(Context.isOnEventLoopThread()); threadName.set(Thread.currentThread().getName()); - fut.complete(null); + return null; }).onComplete(ar -> { testComplete(); }); @@ -69,13 +70,13 @@ public void testOrdered() { for (int i = 0;i < num;i++) { boolean first = i == 0; boolean last = i == num - 1; - worker.executeBlocking(fut -> { + worker.executeBlocking(() -> { if (first) { try { awaitLatch(submitted); } catch (InterruptedException e) { fail(e); - return; + return null; } assertNull(t.get()); t.set(Thread.currentThread()); @@ -83,7 +84,7 @@ public void testOrdered() { assertEquals(t.get(), Thread.currentThread()); } assertTrue(Thread.currentThread().getName().startsWith(poolName + "-")); - fut.complete(null); + return null; }).onComplete(ar -> { if (last) { testComplete(); @@ -106,16 +107,16 @@ public void testUnordered() throws Exception { Context ctx = vertx.getOrCreateContext(); ctx.runOnContext(v -> { for (int i = 0; i < num; i++) { - worker.executeBlocking(fut -> { + worker.executeBlocking(() -> { latch1.countDown(); try { awaitLatch(latch2); } catch (InterruptedException e) { fail(e); - return; + return null; } assertTrue(Thread.currentThread().getName().startsWith(poolName + "-")); - fut.complete(null); + return null; }, false).onComplete(ar -> { complete(); }); @@ -138,7 +139,7 @@ public void start() throws Exception { AtomicReference currentThread = new AtomicReference<>(); for (int i = 0;i < count;i++) { int val = i; - exec.executeBlocking(fut -> { + exec.executeBlocking(() -> { Thread current = Thread.currentThread(); assertNotSame(startThread, current); if (val == 0) { @@ -146,11 +147,11 @@ public void start() throws Exception { } else { assertSame(current, currentThread.get()); } - fut.complete(); + return null; }, true).onComplete(onSuccess(v -> complete())); } } - }, new DeploymentOptions().setWorker(true)).onComplete(onSuccess(v -> {})); + }, new DeploymentOptions().setThreadingModel(ThreadingModel.WORKER)).onComplete(onSuccess(v -> {})); await(); } @@ -163,9 +164,10 @@ public void testPoolSize() throws Exception { CountDownLatch latch1 = new CountDownLatch(poolSize * 100); Set names = Collections.synchronizedSet(new HashSet<>()); for (int i = 0;i < poolSize * 100;i++) { - worker.executeBlocking(fut -> { + worker.executeBlocking(() -> { names.add(Thread.currentThread().getName()); latch1.countDown(); + return null; }, false); } awaitLatch(latch1); @@ -210,13 +212,13 @@ public void testMaxExecuteTime3() { } public void testMaxExecuteTime(WorkerExecutor worker, long maxExecuteTime, TimeUnit maxExecuteTimeUnit) { - worker.executeBlocking(f -> { + worker.executeBlocking(() -> { Thread t = Thread.currentThread(); assertTrue(t instanceof VertxThread); VertxThread thread = (VertxThread) t; assertEquals(maxExecuteTime, thread.maxExecTime()); assertEquals(maxExecuteTimeUnit, thread.maxExecTimeUnit()); - f.complete(); + return null; }).onComplete(res -> { testComplete(); }); @@ -229,8 +231,9 @@ public void testCloseWorkerPool() throws Exception { AtomicReference thread = new AtomicReference<>(); WorkerExecutor worker1 = vertx.createSharedWorkerExecutor(poolName); WorkerExecutor worker2 = vertx.createSharedWorkerExecutor(poolName); - worker1.executeBlocking(fut -> { + worker1.executeBlocking(() -> { thread.set(Thread.currentThread()); + return null; }); assertWaitUntil(() -> thread.get() != null); worker1.close(); @@ -253,7 +256,10 @@ public void start() throws Exception { String deploymentId = deploymentIdRef.get(20, SECONDS); vertx.undeploy(deploymentId).onComplete(onSuccess(v -> { try { - pool.get().executeBlocking(fut -> fail()); + pool.get().executeBlocking(() -> { + fail(); + return null; + }); fail(); } catch (RejectedExecutionException ignore) { testComplete(); @@ -263,7 +269,7 @@ public void start() throws Exception { } @Test - public void testDeployUsingNamedPool() throws Exception { + public void testDeployUsingNamedPool() { AtomicReference thread = new AtomicReference<>(); String poolName = "vert.x-" + TestUtils.randomAlphaString(10); Promise undeployed = Promise.promise(); @@ -271,13 +277,13 @@ public void testDeployUsingNamedPool() throws Exception { @Override public void start() { vertx.runOnContext(v1 -> { - vertx.executeBlocking(fut -> { + vertx.executeBlocking(() -> { thread.set(Thread.currentThread()); assertTrue(Context.isOnVertxThread()); assertTrue(Context.isOnWorkerThread()); assertFalse(Context.isOnEventLoopThread()); assertTrue(Thread.currentThread().getName().startsWith(poolName + "-")); - fut.complete(); + return null; }).onComplete(onSuccess(v2 -> { vertx.undeploy(context.deploymentID()).onComplete(undeployed); })); @@ -287,6 +293,27 @@ public void start() { assertWaitUntil(() -> thread.get() != null && thread.get().getState() == Thread.State.TERMINATED); } + @Test + public void testNamedWorkerPoolShouldBeClosedAfterVerticleIsUndeployed() { + AtomicReference threadName = new AtomicReference<>(); + vertx.deployVerticle(new AbstractVerticle() { + @Override + public void start() { + } + @Override + public void stop() { + threadName.set(Thread.currentThread().getName()); + } + }, new DeploymentOptions().setThreadingModel(ThreadingModel.WORKER).setWorkerPoolName("test-worker")).onComplete(onSuccess(id -> { + vertx.undeploy(id).onComplete(onSuccess(v -> { + assertNotNull(threadName.get()); + assertTrue(threadName.get().startsWith("test-worker")); + testComplete(); + })); + })); + await(); + } + @Test public void testDeployUsingNamedWorkerDoesNotCreateExtraEventLoop() { int instances = getOptions().getEventLoopPoolSize(); @@ -322,7 +349,7 @@ public void start() throws Exception { vertx.undeploy(context.deploymentID()); }); } - }, new DeploymentOptions().setWorker(true).setWorkerPoolName(poolName)).onComplete(onSuccess(deployment::set)); + }, new DeploymentOptions().setThreadingModel(ThreadingModel.WORKER).setWorkerPoolName(poolName)).onComplete(onSuccess(deployment::set)); assertWaitUntil(() -> thread.get() != null && thread.get().getState() == Thread.State.TERMINATED); } @@ -333,12 +360,18 @@ public void testCloseWorkerPoolsWhenVertxCloses() { WorkerExecutor exec = vertx.createSharedWorkerExecutor("vert.x-123"); vertx.close().onComplete(v -> { try { - vertx.executeBlocking(fut -> fail()).onComplete(ar -> fail()); + vertx.executeBlocking(() -> { + fail(); + return null; + }).onComplete(ar -> fail()); fail(); } catch (RejectedExecutionException ignore) { } try { - exec.executeBlocking(fut -> fail()).onComplete(ar -> fail()); + exec.executeBlocking(() -> { + fail(); + return null; + }).onComplete(ar -> fail()); fail(); } catch (RejectedExecutionException ignore) { } @@ -356,7 +389,7 @@ public void testReuseWorkerPoolNameAfterVerticleIsUndeployed() throws Exception vertx.deployVerticle(new AbstractVerticle() { @Override public void start(Promise startPromise) { - vertx.executeBlocking(Promise::complete).onComplete(startPromise); + vertx.executeBlocking(() -> null).onComplete(startPromise); } }, new DeploymentOptions().setWorkerPoolName("foo")).onComplete(onSuccess(id -> { ref.set(id); @@ -372,7 +405,7 @@ public void start(Promise startPromise) { vertx.deployVerticle(new AbstractVerticle() { @Override public void start(Promise startPromise) { - vertx.executeBlocking(Promise::complete).onComplete(startPromise); + vertx.executeBlocking(() -> null).onComplete(startPromise); } }, new DeploymentOptions().setWorkerPoolName("foo")).onComplete(onSuccess(id -> { deployLatch2.countDown(); diff --git a/src/test/java/io/vertx/core/TaskQueueTest.java b/src/test/java/io/vertx/core/TaskQueueTest.java new file mode 100644 index 00000000000..7a4b21fdccc --- /dev/null +++ b/src/test/java/io/vertx/core/TaskQueueTest.java @@ -0,0 +1,202 @@ +package io.vertx.core; + +import io.vertx.core.impl.TaskQueue; +import io.vertx.core.impl.WorkerExecutor; +import io.vertx.test.core.AsyncTestBase; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +public class TaskQueueTest extends AsyncTestBase { + + private TaskQueue taskQueue; + private Executor executor; + private List threads = Collections.synchronizedList(new ArrayList<>()); + + @Override + protected void setUp() throws Exception { + super.setUp(); + taskQueue = new TaskQueue(); + AtomicInteger idx = new AtomicInteger(); + executor = cmd -> { + new Thread(cmd, "vert.x-" + idx.getAndIncrement()).start(); + }; + } + + @Override + protected void tearDown() throws Exception { + try { + for (int i = 0;i < threads.size();i++) { + threads.get(i).join(); + } + } finally { + threads.clear(); + } + super.tearDown(); + } + + private void suspendAndAwaitResume(WorkerExecutor.TaskController controller) { + try { + controller.suspendAndAwaitResume(); + } catch (InterruptedException e) { + fail(e); + } + } + + @Test + public void testCreateThread() throws Exception { + AtomicReference thread = new AtomicReference<>(); + taskQueue.execute(() -> { + thread.set(Thread.currentThread()); + }, executor); + waitUntil(() -> thread.get() != null); + Thread.sleep(10); + taskQueue.execute(() -> { + assertNotSame(thread.get(), Thread.currentThread()); + testComplete(); + }, executor); + await(); + } + + @Test + public void testAwaitSchedulesOnNewThread() { + taskQueue.execute(() -> { + Thread current = Thread.currentThread(); + taskQueue.execute(() -> { + assertNotSame(current, Thread.currentThread()); + testComplete(); + }, executor); + WorkerExecutor.TaskController cont = taskQueue.current(); + suspendAndAwaitResume(cont); + }, executor); + await(); + } + + @Test + public void testResumeFromAnotherThread() { + taskQueue.execute(() -> { + WorkerExecutor.TaskController continuation = taskQueue.current(); + new Thread(() -> { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + continuation.resume(); + suspendAndAwaitResume(continuation); + }).start(); + testComplete(); + }, executor); + await(); + } + + @Test + public void testResumeFromContextThread() { + taskQueue.execute(() -> { + WorkerExecutor.TaskController continuation = taskQueue.current(); + taskQueue.execute(() -> { + // Make sure the awaiting thread will block on the internal future before resolving it (could use thread status) + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + continuation.resume(); + }, executor); + suspendAndAwaitResume(continuation); + testComplete(); + }, executor); + await(); + } + + @Test + public void testResumeWhenIdle() { + taskQueue.execute(() -> { + WorkerExecutor.TaskController cont = taskQueue.current(); + AtomicReference ref = new AtomicReference<>(); + new Thread(() -> { + Thread th; + while ((th = ref.get()) == null) { + try { + Thread.sleep(1); + } catch (InterruptedException ignore) { + } + } + try { + th.join(2_000); + } catch (InterruptedException ignore) { + ignore.printStackTrace(System.out); + } + cont.resume(); + }).start(); + taskQueue.execute(() -> ref.set(Thread.currentThread()), executor); + suspendAndAwaitResume(cont); + testComplete(); + }, executor); + await(); + } + + @Test + public void testRaceResumeBeforeSuspend() { + AtomicInteger seq = new AtomicInteger(); + taskQueue.execute(() -> { + taskQueue.execute(() -> { + WorkerExecutor.TaskController cont = taskQueue.current(); + cont.resume(() -> { + assertEquals(1, seq.getAndIncrement()); + }); + assertEquals(0, seq.getAndIncrement()); + suspendAndAwaitResume(cont); + assertEquals(2, seq.getAndIncrement()); + }, executor); + taskQueue.execute(() -> { + assertEquals(3, seq.getAndIncrement()); + testComplete(); + }, executor); + }, executor); + await(); + } + + // Need to do unschedule when nested test! + + @Test + public void testUnscheduleRace2() { + AtomicInteger seq = new AtomicInteger(); + taskQueue.execute(() -> { + CompletableFuture cf = new CompletableFuture<>(); + taskQueue.execute(() -> { + assertEquals("vert.x-0", Thread.currentThread().getName()); + assertEquals(0, seq.getAndIncrement()); + WorkerExecutor.TaskController cont = taskQueue.current(); + cf.whenComplete((v, e) -> cont.resume(() -> { + assertEquals("vert.x-1", Thread.currentThread().getName()); + assertEquals(2, seq.getAndIncrement()); + })); + suspendAndAwaitResume(cont); + }, executor); + AtomicBoolean enqueued = new AtomicBoolean(); + taskQueue.execute(() -> { + assertEquals("vert.x-1", Thread.currentThread().getName()); + assertEquals(1, seq.getAndIncrement()); + while (!enqueued.get()) { + // Wait until next task is enqueued + } + cf.complete(null); + }, executor); + taskQueue.execute(() -> { + assertEquals("vert.x-0", Thread.currentThread().getName()); + assertEquals(3, seq.getAndIncrement()); + testComplete(); + }, executor); + enqueued.set(true); + }, executor); + + await(); + } +} diff --git a/src/test/java/io/vertx/core/TimerTest.java b/src/test/java/io/vertx/core/TimerTest.java index af2b6004fa6..97b8f795f25 100644 --- a/src/test/java/io/vertx/core/TimerTest.java +++ b/src/test/java/io/vertx/core/TimerTest.java @@ -18,6 +18,7 @@ import io.vertx.test.core.VertxTestBase; import org.junit.Test; +import java.util.concurrent.CancellationException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -171,7 +172,7 @@ public void start() throws Exception { assertTrue(vertx.cancelTimer(id)); testComplete(); } - }, new DeploymentOptions().setWorker(true)); + }, new DeploymentOptions().setThreadingModel(ThreadingModel.WORKER)); await(); } @@ -185,7 +186,7 @@ public void start() throws Exception { testComplete(); }); } - }, new DeploymentOptions().setWorker(true)); + }, new DeploymentOptions().setThreadingModel(ThreadingModel.WORKER)); await(); } @@ -266,30 +267,48 @@ public void testTimerOnContext() { @Test public void testPeriodicOnContext() { + testPeriodicOnContext(((VertxInternal)vertx).createEventLoopContext()); + } + + @Test + public void testPeriodicOnDuplicatedContext() { + testPeriodicOnContext(((VertxInternal)vertx).createEventLoopContext().duplicate()); + } + + private void testPeriodicOnContext(ContextInternal ctx2) { disableThreadChecks(); waitFor(4); ContextInternal ctx1 = ((VertxInternal)vertx).createEventLoopContext(); - ContextInternal ctx2 = ((VertxInternal)vertx).createEventLoopContext(); assertNotSame(ctx1, ctx2); ctx2.runOnContext(v -> { - vertx.setPeriodic(10, new Handler() { + Thread th = Thread.currentThread(); + vertx.setPeriodic(10, new Handler<>() { int count; @Override public void handle(Long l) { - assertSame(ctx2, vertx.getOrCreateContext()); + assertSame(th, Thread.currentThread()); + ContextInternal current = (ContextInternal) vertx.getOrCreateContext(); + assertNotNull(current); + assertTrue(current.isDuplicate()); + assertNotSame(ctx2, current); + assertSame(ctx2.unwrap(), current.unwrap()); if (++count == 2) { vertx.cancelTimer(l); } complete(); } }); - ctx1.setPeriodic(10, new Handler() { + ctx1.setPeriodic(10, new Handler<>() { int count; @Override public void handle(Long l) { - assertSame(ctx1, vertx.getOrCreateContext()); + ContextInternal current = (ContextInternal) vertx.getOrCreateContext(); + assertNotNull(current); + assertTrue(current.isDuplicate()); + assertNotSame(ctx1, current); + assertSame(ctx1, current.unwrap()); if (++count == 2) { vertx.cancelTimer(l); } @@ -340,4 +359,45 @@ public void start() throws Exception { }); await(); } + + @Test + public void testTimerFire() { + long now = System.currentTimeMillis(); + Timer timer = vertx.timer(1, TimeUnit.SECONDS); + timer.onComplete(onSuccess(v -> { + assertTrue(System.currentTimeMillis() - now >= 800); + testComplete(); + })); + await(); + } + + @Test + public void testTimerFireOnContext() { + new Thread(() -> { + Context ctx = vertx.getOrCreateContext(); + Timer timer = vertx.timer(10, TimeUnit.MILLISECONDS); + timer.onComplete(onSuccess(v -> { + assertSame(ctx, Vertx.currentContext()); + testComplete(); + })); + }).start(); + await(); + } + + @Test + public void testFailTimerTaskWhenCancellingTimer() { + Timer timer = vertx.timer(10_000); + assertTrue(timer.cancel()); + waitUntil(timer::failed); + assertTrue(timer.cause() instanceof CancellationException); + } + + @Test + public void testFailTimerTaskWhenClosingVertx() throws Exception { + Vertx vertx = Vertx.vertx(); + Timer timer = vertx.timer(10_000); + awaitFuture(vertx.close()); + waitUntil(timer::failed); + assertTrue(timer.cause() instanceof CancellationException); + } } diff --git a/src/test/java/io/vertx/core/VertxBuilderTest.java b/src/test/java/io/vertx/core/VertxBuilderTest.java new file mode 100644 index 00000000000..29d6be66fde --- /dev/null +++ b/src/test/java/io/vertx/core/VertxBuilderTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core; + +import io.vertx.core.impl.VertxInternal; +import io.vertx.core.spi.VertxTracerFactory; +import io.vertx.core.spi.tracing.VertxTracer; +import io.vertx.core.tracing.TracingOptions; +import io.vertx.test.core.AsyncTestBase; +import io.vertx.test.fakemetrics.FakeVertxMetrics; +import io.vertx.test.faketracer.FakeTracer; +import org.junit.Test; + +public class VertxBuilderTest extends AsyncTestBase { + + @Test + public void testTracerFactoryDoesNotRequireOptions() { + FakeTracer tracer = new FakeTracer(); + Vertx vertx = Vertx.builder().withTracer(options -> tracer).build(); + assertEquals(tracer, ((VertxInternal)vertx).tracer()); + } + + @Test + public void testMetricsFactoryDoesNotRequireOptions() { + FakeVertxMetrics metrics = new FakeVertxMetrics(); + Vertx vertx = Vertx.builder().withMetrics(options -> metrics).build(); + assertEquals(metrics, ((VertxInternal)vertx).metricsSPI()); + } +} diff --git a/src/test/java/io/vertx/core/VertxOptionsTest.java b/src/test/java/io/vertx/core/VertxOptionsTest.java index 17c37444bdd..e39e5a4200a 100644 --- a/src/test/java/io/vertx/core/VertxOptionsTest.java +++ b/src/test/java/io/vertx/core/VertxOptionsTest.java @@ -152,9 +152,6 @@ public void testOptions() { // OK } ClusterManager mgr = new FakeClusterManager(); - assertNull(options.getClusterManager()); - assertEquals(options, options.setClusterManager(mgr)); - assertSame(mgr, options.getClusterManager()); assertFalse(options.isHAEnabled()); assertEquals(options, options.setHAEnabled(true)); assertTrue(options.isHAEnabled()); @@ -249,12 +246,8 @@ public void testCopyOptions() { options.setHAEnabled(haEnabled); options.setQuorumSize(quorumSize); options.setHAGroup(haGroup); - options.setMetricsOptions( - new MetricsOptions(). - setEnabled(metricsEnabled)); - options.setTracingOptions( - new TracingOptions().setFactory(new FakeTracerFactory()) - ); + options.setMetricsOptions(new MetricsOptions().setEnabled(metricsEnabled)); + options.setTracingOptions(new TracingOptions()); options.setWarningExceptionTime(warningExceptionTime); options.setMaxEventLoopExecuteTimeUnit(maxEventLoopExecuteTimeUnit); options.setMaxWorkerExecuteTimeUnit(maxWorkerExecuteTimeUnit); @@ -283,7 +276,6 @@ public void testCopyOptions() { assertEquals(metricsEnabled, metricsOptions.isEnabled()); TracingOptions tracingOptions = options.getTracingOptions(); assertNotNull(tracingOptions); - assertTrue(tracingOptions.getFactory() instanceof FakeTracerFactory); assertEquals(warningExceptionTime, options.getWarningExceptionTime()); assertEquals(maxEventLoopExecuteTimeUnit, options.getMaxEventLoopExecuteTimeUnit()); assertEquals(maxWorkerExecuteTimeUnit, options.getMaxWorkerExecuteTimeUnit()); @@ -340,7 +332,6 @@ public void testJsonOptions() { assertEquals(1000, options.getBlockedThreadCheckInterval()); assertNull(options.getEventBusOptions().getHost()); assertNull(options.getEventBusOptions().getClusterPublicHost()); - assertEquals(null, options.getClusterManager()); assertEquals(2000l * 1000000, options.getMaxEventLoopExecuteTime()); assertEquals(1l * 60 * 1000 * 1000000, options.getMaxWorkerExecuteTime()); assertFalse(options.isHAEnabled()); @@ -424,7 +415,6 @@ public void testJsonOptions() { assertEquals(workerPoolSize, options.getWorkerPoolSize()); assertEquals(blockedThreadCheckInterval, options.getBlockedThreadCheckInterval()); assertEquals(clusterHost, options.getEventBusOptions().getHost()); - assertEquals(null, options.getClusterManager()); assertEquals(maxEventLoopExecuteTime, options.getMaxEventLoopExecuteTime()); assertEquals(maxWorkerExecuteTime, options.getMaxWorkerExecuteTime()); assertEquals(haEnabled, options.isHAEnabled()); diff --git a/src/test/java/io/vertx/core/VertxStartFailureTest.java b/src/test/java/io/vertx/core/VertxStartFailureTest.java index c5775a85053..c31b08c09f7 100644 --- a/src/test/java/io/vertx/core/VertxStartFailureTest.java +++ b/src/test/java/io/vertx/core/VertxStartFailureTest.java @@ -14,6 +14,7 @@ import io.netty.channel.EventLoopGroup; import io.vertx.core.impl.VertxBuilder; import io.vertx.core.impl.transports.JDKTransport; +import io.vertx.core.spi.cluster.ClusterManager; import io.vertx.core.spi.transport.Transport; import io.vertx.core.spi.cluster.NodeListener; import io.vertx.test.core.AsyncTestBase; @@ -45,11 +46,10 @@ public void testEventBusStartFailure() throws Exception { // will trigger java.net.UnknownHostException String hostName = "zoom.zoom.zen.tld"; FakeClusterManager clusterManager = new FakeClusterManager(); - VertxOptions options = new VertxOptions() - .setClusterManager(clusterManager); + VertxOptions options = new VertxOptions(); options.getAddressResolverOptions().addServer(dnsServerAddress.getAddress().getHostAddress() + ":" + dnsServerAddress.getPort()); options.getEventBusOptions().setHost(hostName); - Throwable failure = failStart(options); + Throwable failure = failStart(options, clusterManager); assertTrue("Was expecting failure to be an instance of UnknownHostException", failure instanceof UnknownHostException); } finally { dnsServer.stop(); @@ -65,8 +65,8 @@ public void join(Promise promise) { promise.fail(expected); } }; - VertxOptions options = new VertxOptions().setClusterManager(clusterManager); - Throwable failure = failStart(options); + VertxOptions options = new VertxOptions(); + Throwable failure = failStart(options, clusterManager); assertSame(expected, failure); } @@ -80,8 +80,8 @@ public Map getSyncMap(String name) { throw expected; } }; - VertxOptions options = new VertxOptions().setClusterManager(clusterManager).setHAEnabled(true); - Throwable failure = failStart(options); + VertxOptions options = new VertxOptions().setHAEnabled(true); + Throwable failure = failStart(options, clusterManager); assertSame(expected, failure); } @@ -95,12 +95,12 @@ public void nodeListener(NodeListener listener) { throw expected; } }; - VertxOptions options = new VertxOptions().setClusterManager(clusterManager).setHAEnabled(true); - Throwable failure = failStart(options); + VertxOptions options = new VertxOptions().setHAEnabled(true); + Throwable failure = failStart(options, clusterManager); assertSame(expected, failure); } - private Throwable failStart(VertxOptions options) throws Exception { + private Throwable failStart(VertxOptions options, ClusterManager clusterManager) throws Exception { List loops = new ArrayList<>(); CountDownLatch latch = new CountDownLatch(1); Transport transport = new JDKTransport() { @@ -112,7 +112,7 @@ public EventLoopGroup eventLoopGroup(int type, int nThreads, ThreadFactory threa } }; AtomicReference> resultRef = new AtomicReference<>(); - new VertxBuilder(options).init().findTransport(transport).clusteredVertx().onComplete(ar -> { + new VertxBuilder(options).clusterManager(clusterManager).init().findTransport(transport).clusteredVertx().onComplete(ar -> { resultRef.set(ar); latch.countDown(); }); diff --git a/src/test/java/io/vertx/core/VertxTest.java b/src/test/java/io/vertx/core/VertxTest.java index 83a50a4a383..638ab9efddd 100644 --- a/src/test/java/io/vertx/core/VertxTest.java +++ b/src/test/java/io/vertx/core/VertxTest.java @@ -31,8 +31,6 @@ import java.lang.ref.WeakReference; import java.net.URL; import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -156,12 +154,12 @@ public void testFinalizeHttpClient() throws Exception { closed1.set(true); }); }) - .listen(8080, "localhost") + .listen(HttpTestBase.DEFAULT_HTTP_PORT, "localhost") .onComplete(onSuccess(server -> latch.countDown())); awaitLatch(latch); HttpClient client = vertx.createHttpClient(); // client.connect(1234, "localhost"); - client.request(HttpMethod.GET, 8080, "localhost", "/").onSuccess(req -> { + client.request(HttpMethod.GET, HttpTestBase.DEFAULT_HTTP_PORT, "localhost", "/").onSuccess(req -> { req.send(); }); (((CleanableHttpClient)client).delegate).netClient().closeFuture().onComplete(ar -> { @@ -182,22 +180,15 @@ public void testFinalizeHttpClient() throws Exception { "\r\n" )); long now = System.currentTimeMillis(); - while (true) { + do { assertTrue(System.currentTimeMillis() - now < 20_000); runGC(); - if (ref.get() == null) { - assertTrue(closed1.get()); - break; - } - } - while (true) { + } while (!closed1.get()); + now = System.currentTimeMillis(); + do { assertTrue(System.currentTimeMillis() - now < 20_000); runGC(); - if (ref.get() == null) { - assertTrue(closed2.get()); - break; - } - } + } while (!closed2.get()); } finally { vertx .close() @@ -219,11 +210,11 @@ public void testFinalizeHttpClientWithRequestNotYetSent() throws Exception { "\r\n"); }); }) - .listen(8080, "localhost") + .listen(HttpTestBase.DEFAULT_HTTP_PORT, "localhost") .onComplete(onSuccess(server -> latch.countDown())); awaitLatch(latch); - HttpClient client = vertx.createHttpClient(new HttpClientOptions().setMaxPoolSize(1)); - Future fut = client.request(HttpMethod.GET, 8080, "localhost", "/"); + HttpClient client = vertx.createHttpClient(new PoolOptions().setHttp1MaxSize(1)); + Future fut = client.request(HttpMethod.GET, HttpTestBase.DEFAULT_HTTP_PORT, "localhost", "/"); assertWaitUntil(fut::succeeded); WeakReference ref = new WeakReference<>(client); client = null; @@ -254,10 +245,10 @@ public void testCascadeCloseHttpClient() throws Exception { req.connection().closeHandler(v -> { connected.set(false); }); - }).listen(8080, "localhost")); + }).listen(HttpTestBase.DEFAULT_HTTP_PORT, "localhost")); VertxInternal vertx2 = (VertxInternal) Vertx.vertx(); HttpClient client = vertx2.createHttpClient(); - client.request(HttpMethod.GET, 8080, "localhost", "/").onComplete(onSuccess(req -> { + client.request(HttpMethod.GET, HttpTestBase.DEFAULT_HTTP_PORT, "localhost", "/").onComplete(onSuccess(req -> { req.send(); })); waitUntil(connected::get); @@ -311,23 +302,16 @@ public void testFinalizeNetClient() throws Exception { } socketRef.get().close(); long now = System.currentTimeMillis(); - while (true) { + do { assertTrue(System.currentTimeMillis() - now < 20_000); runGC(); - if (ref.get() == null) { - assertTrue(closed1.get()); - break; - } - } + } while (!closed1.get()); assertEquals(1, shutdownEventCount.get()); - while (true) { + now = System.currentTimeMillis(); + do { assertTrue(System.currentTimeMillis() - now < 20_000); runGC(); - if (ref.get() == null) { - assertTrue(closed2.get()); - break; - } - } + } while (!closed2.get()); } finally { vertx .close() @@ -380,13 +364,13 @@ public void testFinalizeSharedWorkerExecutor() throws Exception { VertxInternal vertx = (VertxInternal) Vertx.vertx(); try { Thread[] threads = new Thread[2]; - vertx.createSharedWorkerExecutor("LeakTest").executeBlocking(promise -> { + vertx.createSharedWorkerExecutor("LeakTest").executeBlocking(() -> { threads[0] = Thread.currentThread(); - promise.complete(); + return null; }).toCompletionStage().toCompletableFuture().get(20, TimeUnit.SECONDS); - vertx.createSharedWorkerExecutor("LeakTest").executeBlocking(promise -> { + vertx.createSharedWorkerExecutor("LeakTest").executeBlocking(() -> { threads[1] = Thread.currentThread(); - promise.complete(); + return null; }).toCompletionStage().toCompletableFuture().get(20, TimeUnit.SECONDS); runGC(); assertFalse(threads[0].isAlive()); @@ -501,18 +485,15 @@ public void testThreadLeak() throws Exception { Vertx vertx = Vertx.vertx(); try { WorkerExecutor exec = vertx.createSharedWorkerExecutor("pool"); - WeakReference ref = exec.>executeBlocking(p -> { - p.complete(new WeakReference<>(Thread.currentThread())); + WeakReference ref = exec.executeBlocking(() -> { + return new WeakReference<>(Thread.currentThread()); }).toCompletionStage().toCompletableFuture().get(); exec.close().toCompletionStage().toCompletableFuture().get(); long now = System.currentTimeMillis(); - while (true) { + do { assertTrue(System.currentTimeMillis() - now < 20_000); runGC(); - if (ref.get() == null) { - break; - } - } + } while (ref.get() != null); } finally { vertx .close() @@ -520,4 +501,9 @@ public void testThreadLeak() throws Exception { } await(); } + + @Test + public void testVersion() { + assertNotNull(VertxInternal.version()); + } } diff --git a/src/test/java/io/vertx/core/VirtualThreadContextTest.java b/src/test/java/io/vertx/core/VirtualThreadContextTest.java new file mode 100644 index 00000000000..42dd9f011c8 --- /dev/null +++ b/src/test/java/io/vertx/core/VirtualThreadContextTest.java @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core; + +import io.vertx.core.impl.ContextInternal; +import io.vertx.core.impl.VertxInternal; +import io.vertx.core.impl.WorkerExecutor; +import io.vertx.core.impl.future.PromiseInternal; +import io.vertx.test.core.VertxTestBase; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; + +public class VirtualThreadContextTest extends VertxTestBase { + + VertxInternal vertx; + + @Before + public void setUp() throws Exception { + super.setUp(); + vertx = (VertxInternal) super.vertx; + } + + @Test + public void testContext() { + Assume.assumeTrue(VertxInternal.isVirtualThreadAvailable()); + vertx.createVirtualThreadContext().runOnContext(v -> { + Thread thread = Thread.currentThread(); + assertTrue(VirtualThreadDeploymentTest.isVirtual(thread)); + ContextInternal context = vertx.getOrCreateContext(); + Executor executor = context.executor(); + assertTrue(executor instanceof WorkerExecutor); + context.runOnContext(v2 -> { + // assertSame(thread, Thread.currentThread()); + assertSame(context, vertx.getOrCreateContext()); + testComplete(); + }); + }); + await(); + } + + @Test + public void testAwaitFutureSuccess() { + Assume.assumeTrue(VertxInternal.isVirtualThreadAvailable()); + Object result = new Object(); + vertx.createVirtualThreadContext().runOnContext(v -> { + ContextInternal context = vertx.getOrCreateContext(); + PromiseInternal promise = context.promise(); + new Thread(() -> { + try { + Thread.sleep(100); + } catch (InterruptedException ignore) { + } + promise.complete(result); + }).start(); + assertSame(result, promise.future().await()); + testComplete(); + }); + await(); + } + + @Test + public void testAwaitFutureFailure() { + Assume.assumeTrue(VertxInternal.isVirtualThreadAvailable()); + Exception failure = new Exception(); + vertx.createVirtualThreadContext().runOnContext(v -> { + ContextInternal context = vertx.getOrCreateContext(); + PromiseInternal promise = context.promise(); + new Thread(() -> { + try { + Thread.sleep(100); + } catch (InterruptedException ignore) { + } + promise.fail(failure); + }).start(); + try { + promise.future().await(); + } catch (Exception e) { + assertSame(failure, e); + testComplete(); + return; + } + fail(); + }); + await(); + } + + @Test + public void testAwaitCompoundFuture() { + Assume.assumeTrue(VertxInternal.isVirtualThreadAvailable()); + Object result = new Object(); + vertx.createVirtualThreadContext().runOnContext(v -> { + ContextInternal context = vertx.getOrCreateContext(); + PromiseInternal promise = context.promise(); + new Thread(() -> { + try { + Thread.sleep(100); + } catch (InterruptedException ignore) { + } + promise.complete(result); + }).start(); + assertSame("HELLO", promise.future().map(res -> "HELLO").await()); + testComplete(); + }); + await(); + } + + @Test + public void testDuplicateUseSameThread() { + Assume.assumeTrue(VertxInternal.isVirtualThreadAvailable()); + int num = 1000; + waitFor(num); + vertx.createVirtualThreadContext().runOnContext(v -> { + ContextInternal context = vertx.getOrCreateContext(); + Thread th = Thread.currentThread(); + for (int i = 0;i < num;i++) { + ContextInternal duplicate = context.duplicate(); + duplicate.runOnContext(v2 -> { + // assertSame(th, Thread.currentThread()); + complete(); + }); + } + }); + await(); + } + + @Test + public void testDuplicateConcurrentAwait() { + Assume.assumeTrue(VertxInternal.isVirtualThreadAvailable()); + int num = 1000; + waitFor(num); + vertx.createVirtualThreadContext().runOnContext(v -> { + ContextInternal context = vertx.getOrCreateContext(); + Object lock = new Object(); + List> list = new ArrayList<>(); + for (int i = 0;i < num;i++) { + ContextInternal duplicate = context.duplicate(); + duplicate.runOnContext(v2 -> { + Promise promise = duplicate.promise(); + boolean complete; + synchronized (lock) { + list.add(promise); + complete = list.size() == num; + } + if (complete) { + context.runOnContext(v3 -> { + synchronized (lock) { + list.forEach(p -> p.complete(null)); + } + }); + } + Future f = promise.future(); + f.await(); + complete(); + }); + } + }); + await(); + } + + @Test + public void testTimer() { + Assume.assumeTrue(VertxInternal.isVirtualThreadAvailable()); + vertx.createVirtualThreadContext().runOnContext(v -> { + ContextInternal context = vertx.getOrCreateContext(); + PromiseInternal promise = context.promise(); + vertx.setTimer(100, id -> { + promise.complete("foo"); + }); + String res = promise.await(); + assertEquals("foo", res); + testComplete(); + }); + await(); + } + + @Test + public void testInThread() { + Assume.assumeTrue(VertxInternal.isVirtualThreadAvailable()); + vertx.createVirtualThreadContext().runOnContext(v1 -> { + ContextInternal context = vertx.getOrCreateContext(); + assertTrue(context.inThread()); + new Thread(() -> { + boolean wasNotInThread = !context.inThread(); + context.runOnContext(v2 -> { + assertTrue(wasNotInThread); + assertTrue(context.inThread()); + testComplete(); + }); + }).start(); + }); + await(); + } + + private void sleep(AtomicInteger inflight) { + assertEquals(0, inflight.getAndIncrement()); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + inflight.decrementAndGet(); + } + } + + @Test + public void testSerializeBlocking() throws Exception { + Assume.assumeTrue(VertxInternal.isVirtualThreadAvailable()); + AtomicInteger inflight = new AtomicInteger(); + vertx.createVirtualThreadContext().runOnContext(v1 -> { + Context ctx = vertx.getOrCreateContext(); + for (int i = 0;i < 10;i++) { + ctx.runOnContext(v2 -> sleep(inflight)); + } + ctx.runOnContext(v -> testComplete()); + }); + await(); + } + + @Test + public void testVirtualThreadsNotAvailable() { + Assume.assumeFalse(VertxInternal.isVirtualThreadAvailable()); + try { + vertx.createVirtualThreadContext(); + fail(); + } catch (IllegalStateException expected) { + } + } +} diff --git a/src/test/java/io/vertx/core/VirtualThreadDeploymentTest.java b/src/test/java/io/vertx/core/VirtualThreadDeploymentTest.java new file mode 100644 index 00000000000..bf1c57a839d --- /dev/null +++ b/src/test/java/io/vertx/core/VirtualThreadDeploymentTest.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core; + +import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.*; +import io.vertx.core.impl.VertxInternal; +import io.vertx.test.core.VertxTestBase; +import junit.framework.AssertionFailedError; +import org.junit.Assert; +import org.junit.Assume; +import org.junit.Test; + +import java.lang.reflect.Method; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +public class VirtualThreadDeploymentTest extends VertxTestBase { + + static { + Method isVirtualMethod = null; + try { + isVirtualMethod = Thread.class.getDeclaredMethod("isVirtual"); + } catch (NoSuchMethodException ignore) { + } + IS_VIRTUAL = isVirtualMethod; + } + + private static final Method IS_VIRTUAL; + + public static boolean isVirtual(Thread th) { + if (IS_VIRTUAL != null) { + try { + return (boolean) IS_VIRTUAL.invoke(th); + } catch (Exception e) { + AssertionFailedError afe = new AssertionFailedError(); + afe.initCause(e); + throw afe; + } + } else { + return false; + } + } + + @Test + public void testDeploy() { + Assume.assumeTrue(VertxInternal.isVirtualThreadAvailable()); + vertx.deployVerticle(new AbstractVerticle() { + @Override + public void start() { + assertTrue(isVirtual(Thread.currentThread())); + Future fut = Future.future(p -> vertx.setTimer(500, id -> p.complete())); + fut.await(); + testComplete(); + } + }, new DeploymentOptions().setThreadingModel(ThreadingModel.VIRTUAL_THREAD)); + await(); + } + + @Test + public void testExecuteBlocking() { + Assume.assumeTrue(VertxInternal.isVirtualThreadAvailable()); + vertx.deployVerticle(new AbstractVerticle() { + @Override + public void start() { + Future fut = vertx.executeBlocking(() -> { + assertTrue(isVirtual(Thread.currentThread())); + return Thread.currentThread().getName(); + }); + String res = fut.await(); + assertNotSame(Thread.currentThread().getName(), res); + testComplete(); + } + }, new DeploymentOptions().setThreadingModel(ThreadingModel.VIRTUAL_THREAD)); + await(); + } + + @Test + public void testDeployHTTPServer() throws Exception { + Assume.assumeTrue(VertxInternal.isVirtualThreadAvailable()); + AtomicInteger inflight = new AtomicInteger(); + AtomicBoolean processing = new AtomicBoolean(); + AtomicInteger max = new AtomicInteger(); + vertx.deployVerticle(new AbstractVerticle() { + HttpServer server; + @Override + public void start() { + server = vertx.createHttpServer().requestHandler(req -> { + assertFalse(processing.getAndSet(true)); + int val = inflight.incrementAndGet(); + max.set(Math.max(val, max.get())); + Future fut = Future.future(p -> vertx.setTimer(50, id -> p.complete())); + processing.set(false); + fut.await(); + assertFalse(processing.getAndSet(true)); + req.response().end(); + inflight.decrementAndGet(); + processing.set(false); + }); + server.listen(HttpTestBase.DEFAULT_HTTP_PORT, "localhost").await(); + } + }, new DeploymentOptions().setThreadingModel(ThreadingModel.VIRTUAL_THREAD)) + .toCompletionStage() + .toCompletableFuture() + .get(); + HttpClient client = vertx.createHttpClient(); + int numReq = 10; + waitFor(numReq); + for (int i = 0;i < numReq;i++) { + Future resp = client.request(HttpMethod.GET, HttpTestBase.DEFAULT_HTTP_PORT, "localhost", "/") + .compose(req -> req.send() + .compose(HttpClientResponse::body)); + resp.onComplete(onSuccess(v -> complete())); + } + await(); + Assert.assertEquals(5, max.get()); + } + + @Test + public void testVirtualThreadsNotAvailable() { + Assume.assumeFalse(VertxInternal.isVirtualThreadAvailable()); + vertx.deployVerticle(new AbstractVerticle() { + @Override + public void start() { + } + }, new DeploymentOptions().setThreadingModel(ThreadingModel.VIRTUAL_THREAD)).onComplete(onFailure(err -> { + testComplete(); + })); + await(); + } +} diff --git a/src/test/java/io/vertx/core/buffer/BufferTest.java b/src/test/java/io/vertx/core/buffer/BufferTest.java index d18a7752fa5..65972e31c76 100644 --- a/src/test/java/io/vertx/core/buffer/BufferTest.java +++ b/src/test/java/io/vertx/core/buffer/BufferTest.java @@ -15,6 +15,7 @@ import io.netty.buffer.Unpooled; import io.netty.util.IllegalReferenceCountException; import io.vertx.core.buffer.impl.BufferImpl; +import io.vertx.core.buffer.impl.BufferInternal; import io.vertx.core.json.DecodeException; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; @@ -42,14 +43,14 @@ private static ByteBuf paddedByteBuf(int padding, byte[] bytes) { } private static final int MEDIUM_MAX_VALUE = 2 << 23; - private static final Function PADDED_BUFFER_FACTORY = arr -> Buffer.buffer(paddedByteBuf(5, arr)); + private static final Function PADDED_BUFFER_FACTORY = arr -> BufferInternal.buffer(paddedByteBuf(5, arr)); @Test public void testConstructorArguments() throws Exception { assertIllegalArgumentException(() -> Buffer.buffer(-1)); assertNullPointerException(() -> Buffer.buffer((byte[]) null)); assertNullPointerException(() -> Buffer.buffer((String) null)); - assertNullPointerException(() -> Buffer.buffer((ByteBuf) null)); + assertNullPointerException(() -> BufferInternal.buffer((ByteBuf) null)); assertNullPointerException(() -> Buffer.buffer(null, "UTF-8")); assertNullPointerException(() -> Buffer.buffer("", null)); } @@ -276,6 +277,8 @@ public void testLE() { checkBEAndLE(4, Buffer.buffer().appendInt(Integer.MAX_VALUE), Buffer.buffer().appendIntLE(Integer.MAX_VALUE)); checkBEAndLE(4, Buffer.buffer().appendUnsignedInt(Integer.MAX_VALUE), Buffer.buffer().appendUnsignedIntLE(Integer.MAX_VALUE)); checkBEAndLE(8, Buffer.buffer().appendLong(Long.MAX_VALUE), Buffer.buffer().appendLongLE(Long.MAX_VALUE)); + checkBEAndLE(4, Buffer.buffer().appendFloat(Float.MAX_VALUE), Buffer.buffer().appendFloatLE(Float.MAX_VALUE)); + checkBEAndLE(8, Buffer.buffer().appendDouble(Double.MAX_VALUE), Buffer.buffer().appendDoubleLE(Double.MAX_VALUE)); } private void checkBEAndLE(int size, Buffer big, Buffer little) { @@ -528,32 +531,67 @@ public void testGetLongLE() throws Exception { testGetSetLong(true); } - @Test - public void testGetFloat() throws Exception { + public void testGetFloat(boolean isLE) throws Exception { int numFloats = 100; Buffer b = Buffer.buffer(numFloats * 4); for (int i = 0; i < numFloats; i++) { - b.setFloat(i * 4, i); + if (isLE) { + b.setFloatLE(i * 4, i); + } else { + b.setFloat(i * 4, i); + } + } for (int i = 0; i < numFloats; i++) { - assertEquals((float) i, b.getFloat(i * 4), 0); + if (isLE) { + assertEquals((float) i, b.getFloatLE(i * 4), 0); + } else { + assertEquals((float) i, b.getFloat(i * 4), 0); + } } } @Test - public void testGetDouble() throws Exception { + public void testGetFloat() throws Exception { + testGetFloat(false); + } + + @Test + public void testGetFloatLE() throws Exception { + testGetFloat(true); + } + + public void testGetDouble(boolean isLE) throws Exception { int numDoubles = 100; Buffer b = Buffer.buffer(numDoubles * 8); for (int i = 0; i < numDoubles; i++) { - b.setDouble(i * 8, i); + if (isLE) { + b.setDoubleLE(i * 8, i); + } else { + b.setDouble(i * 8, i); + } } for (int i = 0; i < numDoubles; i++) { - assertEquals((double) i, b.getDouble(i * 8), 0); + if (isLE) { + assertEquals((double) i, b.getDoubleLE(i * 8), 0); + } else { + assertEquals((double) i, b.getDouble(i * 8), 0); + } } } + @Test + public void testGetDouble() throws Exception { + testGetDouble(false); + } + + @Test + public void testGetDoubleLE() throws Exception { + testGetDouble(true); + } + private void testGetSetShort(boolean isLE) throws Exception { int numShorts = 100; Buffer b = Buffer.buffer(numShorts * 2); @@ -1026,7 +1064,7 @@ public void testSlice1() throws Exception { assertEquals(rand, buff.getLong(0)); buff.appendString(TestUtils.randomUnicodeString(100)); assertEquals(100, sliced.length()); - ByteBuf duplicate = sliced.getByteBuf(); + ByteBuf duplicate = ((BufferInternal)sliced).getByteBuf(); assertEquals(0, duplicate.readerIndex()); assertEquals(100, duplicate.readableBytes()); for (int i = 0; i < 100; i++) { @@ -1051,7 +1089,7 @@ public void testSlice2() throws Exception { assertEquals(rand, buff.getLong(10)); buff.appendString(TestUtils.randomUnicodeString(100)); assertEquals(10, sliced.length()); - ByteBuf duplicate = sliced.getByteBuf(); + ByteBuf duplicate = ((BufferInternal)sliced).getByteBuf(); assertEquals(0, duplicate.readerIndex()); assertEquals(10, duplicate.readableBytes()); for (int i = 0; i < 10; i++) { @@ -1101,14 +1139,14 @@ public void testToJsonArray() throws Exception { @Test public void testLength() throws Exception { byte[] bytes = TestUtils.randomByteArray(100); - Buffer buffer = Buffer.buffer(bytes); - assertEquals(100, Buffer.buffer(buffer.getByteBuf()).length()); + BufferInternal buffer = BufferInternal.buffer(bytes); + assertEquals(100, BufferInternal.buffer(buffer.getByteBuf()).length()); } @Test public void testLength2() throws Exception { byte[] bytes = TestUtils.randomByteArray(100); - assertEquals(90, Buffer.buffer(Unpooled.copiedBuffer(bytes).slice(10, 90)).length()); + assertEquals(90, BufferInternal.buffer(Unpooled.copiedBuffer(bytes).slice(10, 90)).length()); } @Test @@ -1116,7 +1154,7 @@ public void testAppendDoesNotModifyByteBufIndex() throws Exception { ByteBuf buf = Unpooled.copiedBuffer("foobar".getBytes()); assertEquals(0, buf.readerIndex()); assertEquals(6, buf.writerIndex()); - Buffer buffer = Buffer.buffer(buf); + Buffer buffer = BufferInternal.buffer(buf); Buffer other = Buffer.buffer("prefix"); other.appendBuffer(buffer); assertEquals(0, buf.readerIndex()); @@ -1126,7 +1164,7 @@ public void testAppendDoesNotModifyByteBufIndex() throws Exception { @Test public void testAppendExpandsBufferWhenMaxCapacityReached() { - Buffer buff = Buffer.buffer(Unpooled.buffer(0, 8)); + Buffer buff = BufferInternal.buffer(Unpooled.buffer(0, 8)); buff.appendString("Hello World"); } @@ -1134,7 +1172,7 @@ public void testAppendExpandsBufferWhenMaxCapacityReached() { public void testWriteExpandsBufferWhenMaxCapacityReached() { String s = "Hello World"; ByteBuf byteBuf = Unpooled.buffer(0, s.length() - 1); - Buffer buff = Buffer.buffer(byteBuf); + Buffer buff = BufferInternal.buffer(byteBuf); int idx = 0; for (byte b : s.getBytes()) { buff.setByte(idx++, b); @@ -1145,7 +1183,7 @@ public void testWriteExpandsBufferWhenMaxCapacityReached() { public void testSetByteAfterCurrentWriterIndexWithoutExpandingCapacity() { ByteBuf byteBuf = Unpooled.buffer(10, Integer.MAX_VALUE); byteBuf.writerIndex(5); - Buffer buff = Buffer.buffer(byteBuf); + Buffer buff = BufferInternal.buffer(byteBuf); buff.setByte(7, (byte)1); assertEquals(8, buff.length()); } @@ -1154,7 +1192,7 @@ public void testSetByteAfterCurrentWriterIndexWithoutExpandingCapacity() { public void testGetByteBuf() { ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeCharSequence("Hello World", StandardCharsets.UTF_8); - Buffer buff = Buffer.buffer(byteBuf); + BufferInternal buff = BufferInternal.buffer(byteBuf); ByteBuf duplicate = buff.getByteBuf(); duplicate.writerIndex(5); assertEquals(11, byteBuf.writerIndex()); @@ -1199,7 +1237,7 @@ private void checkGetXXXUpperBound(BiFunction f, int siz public void testReadOnlyByteBuf() { String s = "Hello World"; ByteBuf byteBuf = Unpooled.buffer(0, s.length() - 1); - Buffer buff = Buffer.buffer(byteBuf.asReadOnly()); + Buffer buff = BufferInternal.buffer(byteBuf.asReadOnly()); assertSame(buff, buff.copy()); } } diff --git a/src/test/java/io/vertx/core/buffer/impl/VertxBufferTest.java b/src/test/java/io/vertx/core/buffer/impl/VertxBufferTest.java index 2cee9b955b9..5ece7583cb6 100644 --- a/src/test/java/io/vertx/core/buffer/impl/VertxBufferTest.java +++ b/src/test/java/io/vertx/core/buffer/impl/VertxBufferTest.java @@ -73,4 +73,5 @@ public void testDuplicate() { assertEquals(3, duplicate.readerIndex()); assertEquals(0, byteBuf.readerIndex()); } + } diff --git a/src/test/java/io/vertx/core/cli/AmbiguousOptionExceptionTest.java b/src/test/java/io/vertx/core/cli/AmbiguousOptionExceptionTest.java deleted file mode 100644 index ed06a4ec7f7..00000000000 --- a/src/test/java/io/vertx/core/cli/AmbiguousOptionExceptionTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli; - -import org.junit.Test; - -import java.util.Arrays; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Test the {@link AmbiguousOptionException}. - */ -public class AmbiguousOptionExceptionTest { - - @Test - public void testCreation() { - AmbiguousOptionException exception = new AmbiguousOptionException("foo", - Arrays.asList(new Option().setLongName("foobar"), new Option().setLongName("foobaz"))); - assertThat(exception.getOptions()).hasSize(2); - assertThat(exception.getToken()).isEqualTo("foo"); - assertThat(exception.getMessage()) - .contains("Ambiguous argument in command line") - .contains("'foo'") - .contains("foobar").contains("foobaz"); - } - -} diff --git a/src/test/java/io/vertx/core/cli/OptionTest.java b/src/test/java/io/vertx/core/cli/OptionTest.java deleted file mode 100644 index 334873bcbfa..00000000000 --- a/src/test/java/io/vertx/core/cli/OptionTest.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli; - -import io.vertx.test.core.TestUtils; -import io.vertx.test.core.VertxTestBase; -import org.junit.Test; - -/** - * @author Ruslan Sennov - */ -public class OptionTest extends VertxTestBase { - - @Test - public void createAndCopy() { - Option option = new Option() - .setArgName(TestUtils.randomAlphaString(100)) - .addChoice(TestUtils.randomAlphaString(100)) - .setDefaultValue(TestUtils.randomAlphaString(100)) - .setDescription(TestUtils.randomAlphaString(100)) - .setFlag(TestUtils.randomBoolean()) - .setHelp(TestUtils.randomBoolean()) - .setHidden(TestUtils.randomBoolean()) - .setLongName(TestUtils.randomAlphaString(100)) - .setMultiValued(TestUtils.randomBoolean()) - .setRequired(TestUtils.randomBoolean()) - .setShortName(TestUtils.randomAlphaString(100)) - .setSingleValued(TestUtils.randomBoolean()); - - Option copy = new Option(option); - assertEquals(copy.getArgName(), option.getArgName()); - assertEquals(copy.getChoices(), option.getChoices()); - assertEquals(copy.getDefaultValue(), option.getDefaultValue()); - assertEquals(copy.getDescription(), option.getDescription()); - assertEquals(copy.isFlag(), option.isFlag()); - assertEquals(copy.isHelp(), option.isHelp()); - assertEquals(copy.isHidden(), option.isHidden()); - assertEquals(copy.getLongName(), option.getLongName()); - assertEquals(copy.isMultiValued(), option.isMultiValued()); - assertEquals(copy.isRequired(), option.isRequired()); - assertEquals(copy.getShortName(), option.getShortName()); - assertEquals(copy.isSingleValued(), option.isSingleValued()); - - copy = new Option(option.toJson()); - assertEquals(copy.getArgName(), option.getArgName()); - assertEquals(copy.getChoices(), option.getChoices()); - assertEquals(copy.getDefaultValue(), option.getDefaultValue()); - assertEquals(copy.getDescription(), option.getDescription()); - assertEquals(copy.isFlag(), option.isFlag()); - assertEquals(copy.isHelp(), option.isHelp()); - assertEquals(copy.isHidden(), option.isHidden()); - assertEquals(copy.getLongName(), option.getLongName()); - assertEquals(copy.isMultiValued(), option.isMultiValued()); - assertEquals(copy.isRequired(), option.isRequired()); - assertEquals(copy.getShortName(), option.getShortName()); - assertEquals(copy.isSingleValued(), option.isSingleValued()); - } -} diff --git a/src/test/java/io/vertx/core/cli/converters/BooleanConverterTest.java b/src/test/java/io/vertx/core/cli/converters/BooleanConverterTest.java deleted file mode 100644 index 43a2e912cf7..00000000000 --- a/src/test/java/io/vertx/core/cli/converters/BooleanConverterTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.converters; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - - -public class BooleanConverterTest { - - private BooleanConverter converter = BooleanConverter.INSTANCE; - - @Test - public void testYesNo() throws Exception { - assertThat(converter.fromString("yes")).isTrue(); - assertThat(converter.fromString("YeS")).isTrue(); - assertThat(converter.fromString("no")).isFalse(); - assertThat(converter.fromString("nO")).isFalse(); - } - - @Test - public void testOnOff() throws Exception { - assertThat(converter.fromString("on")).isTrue(); - assertThat(converter.fromString("ON")).isTrue(); - assertThat(converter.fromString("off")).isFalse(); - assertThat(converter.fromString("oFf")).isFalse(); - } - - @Test - public void testTrueFalse() throws Exception { - assertThat(converter.fromString("true")).isTrue(); - assertThat(converter.fromString("TruE")).isTrue(); - assertThat(converter.fromString("fALse")).isFalse(); - assertThat(converter.fromString("false")).isFalse(); - } - - @Test - public void testNumbers() throws Exception { - assertThat(converter.fromString("1")).isTrue(); - assertThat(converter.fromString("2")).isFalse(); - assertThat(converter.fromString("0")).isFalse(); - } - - @Test - public void testWithNullAndEmptyString() throws Exception { - assertThat(converter.fromString(null)).isFalse(); - assertThat(converter.fromString("")).isFalse(); - } - - @Test - public void testWithRandomString() throws Exception { - assertThat(converter.fromString("aaaa")).isFalse(); - assertThat(converter.fromString("welcome true")).isFalse(); - assertThat(converter.fromString("true welcome")).isFalse(); - } -} diff --git a/src/test/java/io/vertx/core/cli/converters/CharacterConverterTest.java b/src/test/java/io/vertx/core/cli/converters/CharacterConverterTest.java deleted file mode 100644 index 88268d429c9..00000000000 --- a/src/test/java/io/vertx/core/cli/converters/CharacterConverterTest.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.converters; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - - -public class CharacterConverterTest { - - CharacterConverter converter = CharacterConverter.INSTANCE; - - @Test - public void testFromString() throws Exception { - assertThat(converter.fromString("a")).isEqualTo('a'); - } - - @Test(expected = NullPointerException.class) - public void testWithNull() throws Exception { - converter.fromString(null); - } - - @Test(expected = IllegalArgumentException.class) - public void testWithEmptyString() throws Exception { - converter.fromString(""); - } - - @Test(expected = IllegalArgumentException.class) - public void testWithLongString() throws Exception { - converter.fromString("ab"); - } -} diff --git a/src/test/java/io/vertx/core/cli/converters/ConstructorBasedConverterTest.java b/src/test/java/io/vertx/core/cli/converters/ConstructorBasedConverterTest.java deleted file mode 100644 index 4f84678aac8..00000000000 --- a/src/test/java/io/vertx/core/cli/converters/ConstructorBasedConverterTest.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.converters; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - - -public class ConstructorBasedConverterTest { - - - @Test - public void testGetIfEligible() throws Exception { - assertThat(ConstructorBasedConverter.getIfEligible(Person.class)).isNotNull(); - assertThat(ConstructorBasedConverter.getIfEligible(Object.class)).isNull(); - } - - @Test - public void testFromString() throws Exception { - assertThat(ConstructorBasedConverter.getIfEligible(Person.class).fromString("vertx").name).isEqualTo("vertx"); - } -} diff --git a/src/test/java/io/vertx/core/cli/converters/ConvertersTest.java b/src/test/java/io/vertx/core/cli/converters/ConvertersTest.java deleted file mode 100644 index 26959389520..00000000000 --- a/src/test/java/io/vertx/core/cli/converters/ConvertersTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.converters; - -import org.junit.Test; - -import java.io.File; -import java.net.URL; -import java.util.NoSuchElementException; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - - -public class ConvertersTest { - - @Test - public void testCreatingSingleValueUsingValueOfOnEnumeration() { - assertThat(Converters.create(HttpMethod.class, "GET")).isEqualTo(HttpMethod.GET); - try { - assertThat(Converters.create(HttpMethod.class, null)).isNull(); - fail("Exception expected"); - } catch (IllegalArgumentException e) { - // OK. - } - - // Invalid value - try { - assertThat(Converters.create(HttpMethod.class, "FOO_IS_NOT_A_METHOD")).isNull(); - fail("Exception expected"); - } catch (IllegalArgumentException e) { - // OK. - } - } - - @Test - public void testWithString() throws NoSuchMethodException { - assertThat(Converters.create(String.class, "Hello")).isEqualTo("Hello"); - assertThat(Converters.create(String.class, "")).isEqualTo(""); - } - - @Test - public void testWithPrimitives() throws NoSuchMethodException { - assertThat(Converters.create(Integer.class, "1")).isEqualTo(1); - assertThat(Converters.create(Integer.TYPE, "1")).isEqualTo(1); - - assertThat(Converters.create(Long.class, "2")).isEqualTo(2l); - assertThat(Converters.create(Long.TYPE, "2")).isEqualTo(2l); - - assertThat(Converters.create(Short.class, "3")).isEqualTo((short) 3); - assertThat(Converters.create(Short.TYPE, "3")).isEqualTo((short) 3); - - assertThat(Converters.create(Byte.class, "4")).isEqualTo((byte) 4); - assertThat(Converters.create(Byte.TYPE, "4")).isEqualTo((byte) 4); - - assertThat(Converters.create(Float.class, "5.5")).isEqualTo(5.5f); - assertThat(Converters.create(Float.TYPE, "5.5")).isEqualTo(5.5f); - - assertThat(Converters.create(Double.class, "5.5")).isEqualTo(5.5d); - assertThat(Converters.create(Double.TYPE, "5.5")).isEqualTo(5.5d); - - assertThat(Converters.create(Character.class, "a")).isEqualTo('a'); - assertThat(Converters.create(Character.TYPE, "a")).isEqualTo('a'); - - assertThat(Converters.create(Boolean.class, "true")).isTrue(); - assertThat(Converters.create(Boolean.TYPE, "on")).isTrue(); - assertThat(Converters.create(Boolean.class, "")).isFalse(); - } - - @Test - public void testUsingFrom() throws NoSuchMethodException { - assertThat(Converters.create(Person2.class, "vertx").name).isEqualTo("vertx"); - } - - @Test - public void testUsingFromString() throws NoSuchMethodException { - assertThat(Converters.create(Person3.class, "vertx").name).isEqualTo("vertx"); - } - - @Test(expected = NoSuchElementException.class) - public void testMissingConvertion() throws NoSuchMethodException { - Converters.create(Object.class, "hello"); - } - - @Test - public void testWithURL() { - final URL url = Converters.create(URL.class, "http://vertx.io"); - assertThat(url.toExternalForm()).isEqualToIgnoringCase("http://vertx.io"); - } - - @Test - public void testWithFile() { - final File file = Converters.create(File.class, "foo/hello.txt"); - assertThat(file).hasName("hello.txt") - .doesNotExist(); - } - - private enum HttpMethod { - GET - } - -} diff --git a/src/test/java/io/vertx/core/cli/converters/CustomConverterTest.java b/src/test/java/io/vertx/core/cli/converters/CustomConverterTest.java deleted file mode 100644 index c307f5338f9..00000000000 --- a/src/test/java/io/vertx/core/cli/converters/CustomConverterTest.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.converters; - -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - - -public class CustomConverterTest { - - @Test - public void testCreation() { - assertThat(new Person4Converter().fromString("bob, morane").first).isEqualToIgnoringCase("bob"); - assertThat(new Person4Converter().fromString("bob, morane").last).isEqualToIgnoringCase("morane"); - } - - @Test - public void testConvertion() { - Person4 p4 = Converters.create("bob, morane", new Person4Converter()); - assertThat(p4.first).isEqualTo("bob"); - assertThat(p4.last).isEqualTo("morane"); - } - -} diff --git a/src/test/java/io/vertx/core/cli/converters/Person.java b/src/test/java/io/vertx/core/cli/converters/Person.java deleted file mode 100644 index d54ca912152..00000000000 --- a/src/test/java/io/vertx/core/cli/converters/Person.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.converters; - - -public class Person { - - public final String name; - - /** - * A constructor that can be used by the engine. - * - * @param n the name - */ - public Person(String n) { - this.name = n; - } -} diff --git a/src/test/java/io/vertx/core/cli/converters/Person2.java b/src/test/java/io/vertx/core/cli/converters/Person2.java deleted file mode 100644 index 0c2e3da1e51..00000000000 --- a/src/test/java/io/vertx/core/cli/converters/Person2.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.converters; - - -public class Person2 { - - public final String name; - - private Person2(String n) { - this.name = n; - } - - public static Person2 from(String n) { - return new Person2(n); - } -} diff --git a/src/test/java/io/vertx/core/cli/converters/Person3.java b/src/test/java/io/vertx/core/cli/converters/Person3.java deleted file mode 100644 index dcbea9655f3..00000000000 --- a/src/test/java/io/vertx/core/cli/converters/Person3.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.converters; - - -public class Person3 { - - public final String name; - - private Person3(String n) { - this.name = n; - } - - public static Person3 fromString(String n) { - return new Person3(n); - } -} diff --git a/src/test/java/io/vertx/core/cli/converters/Person4.java b/src/test/java/io/vertx/core/cli/converters/Person4.java deleted file mode 100644 index 1cb96a0a562..00000000000 --- a/src/test/java/io/vertx/core/cli/converters/Person4.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.converters; - - -public class Person4 { - - public final String first; - public final String last; - - public Person4(String f, String l) { - this.first = f; - this.last = l; - } -} diff --git a/src/test/java/io/vertx/core/cli/impl/CLIConfiguratorTest.java b/src/test/java/io/vertx/core/cli/impl/CLIConfiguratorTest.java deleted file mode 100644 index a3c3de883aa..00000000000 --- a/src/test/java/io/vertx/core/cli/impl/CLIConfiguratorTest.java +++ /dev/null @@ -1,623 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.cli.impl; - - -import io.vertx.core.cli.*; -import io.vertx.core.cli.Option; -import io.vertx.core.cli.annotations.Argument; -import io.vertx.core.cli.annotations.*; -import io.vertx.core.cli.converters.*; -import org.junit.Test; - -import java.util.*; -import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Fail.fail; - -public class CLIConfiguratorTest { - - @Test - public void testHelloCLIFromClass() { - CLI command = CLIConfigurator.define(HelloClI.class); - - assertThat(command.getOptions()).hasSize(1); - TypedOption option = (TypedOption) find(command.getOptions(), "name"); - assertThat(option.getLongName()).isEqualToIgnoringCase("name"); - assertThat(option.getShortName()).isEqualToIgnoringCase("n"); - assertThat(option.getType()).isEqualTo(String.class); - assertThat(option.getArgName()).isEqualTo("name"); - assertThat(option.getDescription()).isEqualToIgnoringCase("your name"); - assertThat(option.getDefaultValue()).isNull(); - assertThat(option.acceptValue()).isTrue(); - assertThat(option.isMultiValued()).isFalse(); - assertThat(option.isRequired()).isTrue(); - } - - @Test - public void testUsage() { - CLI command = CLIConfigurator.define(HelloClI.class); - StringBuilder builder = new StringBuilder(); - command.usage(builder); - assertThat(builder.toString()) - .containsIgnoringCase("Usage: hello -n ") - .containsIgnoringCase("A command saying hello.") - .containsIgnoringCase("A simple cli to wish you a good day. Pass your name with `--name`") - .containsIgnoringCase(" -n,--name your name"); - } - - @Name("test") - public static class CommandForDefaultValueTest { - @io.vertx.core.cli.annotations.Option(longName = "option", shortName = "o") - @DefaultValue("bar") - public void setFoo(String foo) { - } - } - - @Test - public void testOptionsWithDefaultValue() { - CLI cli = CLIConfigurator.define(CommandForDefaultValueTest.class); - - assertThat(cli.getOptions()).hasSize(1); - assertThat(find(cli.getOptions(), "option").getDefaultValue()).isEqualTo("bar"); - assertThat(find(cli.getOptions(), "option").getName()).isEqualTo("option"); - } - - @Name("test") - public static class CommandForDescriptionTest { - @io.vertx.core.cli.annotations.Option(longName = "option", shortName = "o") - @Description("This option is awesome") - public void setFoo(String foo) { - } - } - - @Test - public void testOptionsWithDescription() { - CLI cli = CLIConfigurator.define(CommandForDescriptionTest.class); - - assertThat(cli.getOptions()).hasSize(1); - assertThat(find(cli.getOptions(), "option").getDescription()) - .isEqualTo("This option is awesome"); - } - - @Name("test") - public static class CommandForParsedAsList { - @io.vertx.core.cli.annotations.Option(longName = "option", shortName = "o") - @ParsedAsList(separator = ":") - public void setFoo(List foo) { - - } - } - - @Test - public void testOptionsParsedAsList() { - CLI command = CLIConfigurator.define(CommandForParsedAsList.class); - assertThat(command.getOptions()).hasSize(1); - assertThat(((TypedOption) find(command.getOptions(), "option")) - .getListSeparator()).isEqualTo(":"); - assertThat(find(command.getOptions(), "option").isMultiValued()).isTrue(); - assertThat(((TypedOption) find(command.getOptions(), "option")).getType()) - .isEqualTo(String.class); - } - - @Name("test") - public static class CommandForTypeExtractTest { - @io.vertx.core.cli.annotations.Option(longName = "list", shortName = "l") - public void setFoo(List list) { - } - - @io.vertx.core.cli.annotations.Option(longName = "set", shortName = "s") - public void setFoo(Set set) { - } - - @io.vertx.core.cli.annotations.Option(longName = "collection", shortName = "c") - public void setFoo(Collection collection) { - } - - @io.vertx.core.cli.annotations.Option(longName = "tree", shortName = "t") - public void setFoo(TreeSet list) { - } - - @io.vertx.core.cli.annotations.Option(longName = "al", shortName = "al") - public void setFoo(ArrayList list) { - } - - @io.vertx.core.cli.annotations.Option(longName = "array", shortName = "a") - public void setFoo(int[] list) { - } - } - - @Test - public void testTypeExtraction() { - CLI command = CLIConfigurator.define(CommandForTypeExtractTest.class); - - assertThat(command.getOptions()).hasSize(6); - TypedOption model = (TypedOption) find(command.getOptions(), "list"); - assertThat(model.getType()).isEqualTo(String.class); - assertThat(model.isMultiValued()).isTrue(); - - model = (TypedOption) find(command.getOptions(), "set"); - assertThat(model.getType()).isEqualTo(Character.class); - assertThat(model.isMultiValued()).isTrue(); - - model = (TypedOption) find(command.getOptions(), "collection"); - assertThat(model.getType()).isEqualTo(Integer.class); - assertThat(model.isMultiValued()).isTrue(); - - model = (TypedOption) find(command.getOptions(), "tree"); - assertThat(model.getType()).isEqualTo(String.class); - assertThat(model.isMultiValued()).isTrue(); - - model = (TypedOption) find(command.getOptions(), "al"); - assertThat(model.getType()).isEqualTo(String.class); - assertThat(model.isMultiValued()).isTrue(); - - model = (TypedOption) find(command.getOptions(), "array"); - assertThat(model.getType()).isEqualTo(Integer.TYPE); - assertThat(model.isMultiValued()).isTrue(); - } - - @Test - public void testInjectionOfString() throws CLIException { - HelloClI command = new HelloClI(); - CLI cli = CLIConfigurator.define(HelloClI.class); - CommandLine evaluatedCLI = cli.parse(Arrays.asList("--name", "vert.x")); - CLIConfigurator.inject(evaluatedCLI, command); - - assertThat(command.run()).isEqualToIgnoringCase("Hello vert.x"); - assertThat(command.name).isEqualToIgnoringCase("vert.x"); - } - - private CommandLine parse(CLI cli, String... args) throws CLIException { - return cli.parse(Arrays.asList(args)); - } - - @Test - public void testSingleValueInjection() throws CLIException { - CLIWithSingleValue command = new CLIWithSingleValue(); - CLI cli = CLIConfigurator.define(command.getClass()); - CommandLine evaluatedCLI = parse(cli, "--boolean", "--short=1", "--byte=1", "--int=1", "--long=1", - "--double=1.1", "--float=1.1", "--char=c", "--string=hello"); - CLIConfigurator.inject(evaluatedCLI, command); - - assertThat(command.aBoolean).isTrue(); - assertThat(command.acceptValueBoolean).isTrue(); - assertThat(command.aShort).isEqualTo((short) 1); - assertThat(command.aByte).isEqualTo((byte) 1); - assertThat(command.anInt).isEqualTo(1); - assertThat(command.aLong).isEqualTo(1l); - assertThat(command.aDouble).isEqualTo(1.1d); - assertThat(command.aFloat).isEqualTo(1.1f); - assertThat(command.aChar).isEqualTo('c'); - assertThat(command.aString).isEqualTo("hello"); - - evaluatedCLI = parse(cli, "--boolean2", "--acceptValueBoolean=false", "--short2=1", "--byte2=1", "--int2=1", "--long2=1", - "--double2=1.1", "--float2=1.1", "--char2=c", "--string=hello"); - CLIConfigurator.inject(evaluatedCLI, command); - - assertThat(command.anotherBoolean).isTrue(); - assertThat(command.acceptValueBoolean).isFalse(); - assertThat(command.anotherShort).isEqualTo((short) 1); - assertThat(command.anotherByte).isEqualTo((byte) 1); - assertThat(command.anotherInt).isEqualTo(1); - assertThat(command.anotherLong).isEqualTo(1l); - assertThat(command.anotherDouble).isEqualTo(1.1d); - assertThat(command.anotherFloat).isEqualTo(1.1f); - assertThat(command.anotherChar).isEqualTo('c'); - assertThat(command.aString).isEqualTo("hello"); - - evaluatedCLI = parse(cli, "--acceptValueBoolean=xxx"); - CLIConfigurator.inject(evaluatedCLI, command); - assertThat(command.acceptValueBoolean).isFalse(); - evaluatedCLI = parse(cli, "--acceptValueBoolean=true"); - CLIConfigurator.inject(evaluatedCLI, command); - assertThat(command.acceptValueBoolean).isTrue(); - - evaluatedCLI = parse(cli, "--state=NEW"); - CLIConfigurator.inject(evaluatedCLI, command); - assertThat(command.aState).isEqualTo(Thread.State.NEW); - - evaluatedCLI = parse(cli, "--person=vert.x"); - CLIConfigurator.inject(evaluatedCLI, command); - assertThat(command.aPerson.name).isEqualTo("vert.x"); - - evaluatedCLI = parse(cli, "--person2=vert.x"); - CLIConfigurator.inject(evaluatedCLI, command); - assertThat(command.anotherPerson.name).isEqualTo("vert.x"); - - evaluatedCLI = parse(cli, "--person3=vert.x"); - CLIConfigurator.inject(evaluatedCLI, command); - assertThat(command.aThirdPerson.name).isEqualTo("vert.x"); - - evaluatedCLI = parse(cli, "--person4=bob,morane"); - CLIConfigurator.inject(evaluatedCLI, command); - assertThat(command.aFourthPerson.first).isEqualTo("bob"); - assertThat(command.aFourthPerson.last).isEqualTo("morane"); - } - - @Test - public void testMultiValuesInjection() throws CLIException { - CLIWithMultipleValues command = new CLIWithMultipleValues(); - CLI cli = CLIConfigurator.define(command.getClass()); - - CommandLine evaluatedCLI = parse(cli, "--persons=x", "--persons", "y", "z"); - CLIConfigurator.inject(evaluatedCLI, command); - assertThat(command.persons).hasSize(3); - - evaluatedCLI = parse(cli, "--persons2=x", "--persons2", "y", "z"); - CLIConfigurator.inject(evaluatedCLI, command); - assertThat(command.persons2).hasSize(3); - - evaluatedCLI = parse(cli, "--persons3=x", "--persons3", "y", "z"); - CLIConfigurator.inject(evaluatedCLI, command); - assertThat(command.persons3).hasSize(3); - - evaluatedCLI = parse(cli, "--persons4=x:y:z"); - CLIConfigurator.inject(evaluatedCLI, command); - assertThat(command.persons4).hasSize(3); - - evaluatedCLI = parse(cli, "--states=NEW", "--states", "BLOCKED", "RUNNABLE"); - CLIConfigurator.inject(evaluatedCLI, command); - assertThat(command.states).hasSize(3).containsExactly(Thread.State.NEW, Thread.State.BLOCKED, - Thread.State.RUNNABLE); - - evaluatedCLI = parse(cli, "--ints=1", "--ints", "2", "3"); - CLIConfigurator.inject(evaluatedCLI, command); - assertThat(command.ints).hasSize(3).containsExactly(1, 2, 3); - - evaluatedCLI = parse(cli, "--shorts=1", "--shorts", "2", "3"); - CLIConfigurator.inject(evaluatedCLI, command); - assertThat(command.shorts).hasSize(3).containsExactly((short) 1, (short) 2, (short) 3); - - evaluatedCLI = parse(cli, "--strings=a"); - CLIConfigurator.inject(evaluatedCLI, command); - assertThat(command.strings).hasSize(1).containsExactly("a"); - - evaluatedCLI = parse(cli, "--doubles=1", "--doubles", "2.2", "3.3"); - CLIConfigurator.inject(evaluatedCLI, command); - assertThat(command.doubles).hasSize(3).containsExactly(1.0, 2.2, 3.3); - } - - @Name("test") - public static class CommandForArgumentInjectionTest { - - AtomicReference reference = new AtomicReference<>(); - - @Argument(index = 0) - public void setX(String s) { - reference.set(s); - } - } - - @Test - public void testArgumentInjection() throws CLIException { - CommandForArgumentInjectionTest command = new CommandForArgumentInjectionTest(); - CLI cli = CLIConfigurator.define(command.getClass()).setName("test"); - CommandLine evaluatedCLI = parse(cli, "foo"); - CLIConfigurator.inject(evaluatedCLI, command); - assertThat(command.reference.get()).isEqualTo("foo"); - } - - @Name("test") - public class CommandForConvertedValueTest { - AtomicReference reference = new AtomicReference<>(); - - @Argument(index = 0, required = false) - @DefaultValue("Bill,Balantine") - @ConvertedBy(Person4Converter.class) - public void setX(Person4 s) { - reference.set(s); - } - } - - @Test - public void testArgumentInjectionWithConvertedByAndDefaultValue() throws CLIException { - CommandForConvertedValueTest command = new CommandForConvertedValueTest(); - - CLI cli = CLIConfigurator.define(command.getClass()).setName("test"); - CommandLine evaluatedCLI = parse(cli, "Bob,Morane"); - CLIConfigurator.inject(evaluatedCLI, command); - assertThat(command.reference.get().first).isEqualTo("Bob"); - assertThat(command.reference.get().last).isEqualTo("Morane"); - - evaluatedCLI = parse(cli); - CLIConfigurator.inject(evaluatedCLI, command); - assertThat(command.reference.get().first).isEqualTo("Bill"); - assertThat(command.reference.get().last).isEqualTo("Balantine"); - } - - @Name("test") - public static class CommandForMultipleArgumentTest { - - AtomicReference x = new AtomicReference<>(); - AtomicReference y = new AtomicReference<>(); - - @Argument(index = 0) - public void setX(String s) { - x.set(s); - } - - @Argument(index = 1) - public void setY(int s) { - y.set(s); - } - } - - @Test - public void testArgumentInjectionWithSeveralArguments() throws CLIException { - CommandForMultipleArgumentTest command = new CommandForMultipleArgumentTest(); - CLI cli = CLIConfigurator.define(command.getClass()).setName("test"); - CommandLine evaluatedCLI = parse(cli, "foo", "1"); - CLIConfigurator.inject(evaluatedCLI, command); - assertThat(command.x.get()).isEqualTo("foo"); - assertThat(command.y.get()).isEqualTo(1); - } - - @Name("test") - public static class CommandWithDefaultValueOnArgument { - AtomicReference x = new AtomicReference<>(); - AtomicReference y = new AtomicReference<>(); - - @Argument(index = 0) - public void setX(String s) { - x.set(s); - } - - @Argument(index = 1, required = false) - @DefaultValue("25") - public void setY(int s) { - y.set(s); - } - } - - @Test - public void testArgumentWithDefaultValue() throws CLIException { - CommandWithDefaultValueOnArgument command = new CommandWithDefaultValueOnArgument(); - - CLI cli = CLIConfigurator.define(command.getClass()).setName("test"); - CommandLine evaluatedCLI = parse(cli, "foo"); - CLIConfigurator.inject(evaluatedCLI, command); - assertThat(command.x.get()).isEqualTo("foo"); - assertThat(command.y.get()).isEqualTo(25); - } - - private Option find(List

- * It defines two hidden {@link Option}s to create system properties ({@code -Dkey=value}) and a way to - * configure the current working directory. - */ -public abstract class DefaultCommand implements Command { - - private File cwd; - protected List systemProperties; - - /** - * The execution context of the command. - */ - protected ExecutionContext executionContext; - - /** - * The {@link PrintStream} that the command can use to write on the console. - */ - protected PrintStream out; - - /** - * @return the configure current working directory. If not set use the "regular" Java current working - * directory. - */ - public File getCwd() { - return cwd != null ? cwd : new File("."); - } - - /** - * Sets the current working directory. This method is called when the user configure the "cwd" option as - * follows: {@code --cwd=the-directory}. - * - * @param cwd the directory - */ - @SuppressWarnings("unused") - @Option(longName = "cwd", argName = "dir") - @Description("Specifies the current working directory for this command, default set to the Java current directory") - @Hidden - public void setCwd(File cwd) { - this.cwd = cwd; - } - - /** - * Gets system properties passed in the user command line. The user can configure system properties using - * {@code -Dkey=value}. - * - * @param props the properties - */ - @SuppressWarnings("unused") - @Option(longName = "systemProperty", shortName = "D", argName = "key>= props) { - this.systemProperties = props; - } - - @Override - public void setUp(ExecutionContext ec) throws CLIException { - this.executionContext = ec; - this.out = executionContext.getPrintStream(); - applySystemProperties(); - } - - /** - * @return the print stream on which message should be written. - */ - public PrintStream out() { - return executionContext.getPrintStream(); - } - - @Override - public void tearDown() throws CLIException { - // Default implementation - does nothing. - } - - /** - * Sets the system properties specified by the user command line. - */ - protected void applySystemProperties() { - if (systemProperties != null) { - for (String prop : systemProperties) { - int p = prop.indexOf('='); - if (p > 0) { - String key = prop.substring(0, p); - String val = prop.substring(p + 1); - System.setProperty(key, val); - } - } - } - } -} diff --git a/src/main/java/io/vertx/core/spi/launcher/DefaultCommandFactory.java b/src/main/java/io/vertx/core/spi/launcher/DefaultCommandFactory.java deleted file mode 100644 index 54e14480b5b..00000000000 --- a/src/main/java/io/vertx/core/spi/launcher/DefaultCommandFactory.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.spi.launcher; - -import io.vertx.core.cli.CLI; -import io.vertx.core.cli.CommandLine; -import io.vertx.core.cli.annotations.CLIConfigurator; -import io.vertx.core.cli.impl.ReflectionUtils; - -import java.util.function.Supplier; - -/** - * Default implementation of {@link CommandFactory}. This implementation defines the {@link CLI} from the - * given {@link Command} implementation (by reading the annotation). Then, {@link Command} instance are - * created by calling an empty constructor on the given {@link Command} implementation. - * - * @author Clement Escoffier - */ -public class DefaultCommandFactory implements CommandFactory { - - private final Class clazz; - private final Supplier supplier; - - /** - * Creates a new {@link CommandFactory}. - * - * @param clazz the {@link Command} implementation - * @deprecated Please use {@link #DefaultCommandFactory(Class, Supplier)} - */ - @Deprecated - public DefaultCommandFactory(Class clazz) { - this(clazz, () -> ReflectionUtils.newInstance(clazz)); - } - - /** - * Creates a new {@link CommandFactory}. - * - * @param clazz the {@link Command} implementation - * @param supplier the {@link Command} implementation - */ - public DefaultCommandFactory(Class clazz, Supplier supplier) { - this.clazz = clazz; - this.supplier = supplier; - } - - /** - * @return a new instance of the command by invoking the default constructor of the given class. - */ - @Override - public C create(CommandLine cl) { - return supplier.get(); - } - - /** - * @return the {@link CLI} instance by reading the annotation. - */ - @Override - public CLI define() { - return CLIConfigurator.define(clazz); - } -} diff --git a/src/main/java/io/vertx/core/spi/launcher/ExecutionContext.java b/src/main/java/io/vertx/core/spi/launcher/ExecutionContext.java deleted file mode 100644 index 9287ff4a14f..00000000000 --- a/src/main/java/io/vertx/core/spi/launcher/ExecutionContext.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 - * which is available at https://www.apache.org/licenses/LICENSE-2.0. - * - * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 - */ - -package io.vertx.core.spi.launcher; - -import io.vertx.core.cli.CLI; -import io.vertx.core.cli.CommandLine; -import io.vertx.core.impl.launcher.VertxCommandLauncher; - -import java.io.PrintStream; -import java.util.HashMap; - -/** - * The execution context contains various information on the execution. - */ -public class ExecutionContext extends HashMap { - private final VertxCommandLauncher launcher; - private final Command command; - private final CommandLine commandLine; - - /** - * Creates a new instance of {@link ExecutionContext}. - * - * @param command the command instance that is executed - * @param launcher the launcher class - * @param commandLine the command line - */ - public ExecutionContext(Command command, VertxCommandLauncher launcher, CommandLine commandLine) { - this.command = command; - this.commandLine = commandLine; - this.launcher = launcher; - } - - /** - * @return the command line object. - */ - public Command command() { - return command; - } - - /** - * @return the launcher. - */ - public VertxCommandLauncher launcher() { - return launcher; - } - - /** - * @return the {@link CLI}. - */ - public CLI cli() { - return commandLine.cli(); - } - - /** - * @return the {@link CommandLine}. - */ - public CommandLine commandLine() { - return commandLine; - } - - /** - * Executes another command. - * - * @param command the command name - * @param args the arguments - */ - public void execute(String command, String... args) { - launcher.execute(command, args); - } - - /** - * @return the {@code Main-Class} object. - */ - public Object main() { - return get("Main"); - } - - /** - * @return the {@link PrintStream} on which command can write. - */ - public PrintStream getPrintStream() { - return launcher.getPrintStream(); - } -} diff --git a/src/main/java/io/vertx/core/spi/loadbalancing/DefaultEndpointMetrics.java b/src/main/java/io/vertx/core/spi/loadbalancing/DefaultEndpointMetrics.java new file mode 100644 index 00000000000..3c29c9b2215 --- /dev/null +++ b/src/main/java/io/vertx/core/spi/loadbalancing/DefaultEndpointMetrics.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.spi.loadbalancing; + +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.LongAdder; + +public class DefaultEndpointMetrics implements Endpoint, EndpointMetrics { + + private final E endpoint; + private final LongAdder numberOfInflightRequests = new LongAdder(); + private final LongAdder numberOfRequests = new LongAdder(); + private final LongAdder numberOfFailures = new LongAdder(); + private final AtomicLong minResponseTime = new AtomicLong(Long.MAX_VALUE); + private final AtomicLong maxResponseTime = new AtomicLong(0); + + public DefaultEndpointMetrics(E endpoint) { + this.endpoint = endpoint; + } + + @Override + public E endpoint() { + return endpoint; + } + + @Override + public EndpointMetrics metrics() { + return this; + } + + @Override + public RequestMetric initiateRequest() { + numberOfInflightRequests.increment(); + numberOfRequests.increment(); + return new RequestMetric(); + } + + @Override + public void reportFailure(RequestMetric metric, Throwable failure) { + if (metric.failure == null) { + metric.failure = failure; + numberOfInflightRequests.decrement(); + numberOfFailures.increment(); + } + } + + @Override + public void reportRequestBegin(RequestMetric metric) { + metric.requestBegin = System.currentTimeMillis(); + } + + @Override + public void reportRequestEnd(RequestMetric metric) { + metric.requestEnd = System.currentTimeMillis(); + } + + @Override + public void reportResponseBegin(RequestMetric metric) { + metric.responseBegin = System.currentTimeMillis(); + } + + @Override + public void reportResponseEnd(RequestMetric metric) { + metric.responseEnd = System.currentTimeMillis(); + if (metric.failure == null) { + reportRequestMetric(metric); + numberOfInflightRequests.decrement(); + } + } + + void reportRequestMetric(RequestMetric metric) { + long responseTime = metric.responseEnd - metric.requestBegin; + while (true) { + long val = minResponseTime.get(); + if (responseTime >= val || minResponseTime.compareAndSet(val, responseTime)) { + break; + } + } + while (true) { + long val = maxResponseTime.get(); + if (responseTime <= val || maxResponseTime.compareAndSet(val, responseTime)) { + break; + } + } + } + + /** + * @return the number of inflight requests + */ + public int numberOfInflightRequests() { + return numberOfInflightRequests.intValue(); + } + + /** + * @return the total number of requests + */ + public int numberOfRequests() { + return numberOfRequests.intValue(); + } + + /** + * @return the total number of failures + */ + public int numberOfFailures() { + return numberOfFailures.intValue(); + } + + public int minResponseTime() { + return minResponseTime.intValue(); + } + + public int maxResponseTime() { + return maxResponseTime.intValue(); + } + +} diff --git a/src/main/java/io/vertx/core/spi/loadbalancing/Endpoint.java b/src/main/java/io/vertx/core/spi/loadbalancing/Endpoint.java new file mode 100644 index 00000000000..8d5d9652084 --- /dev/null +++ b/src/main/java/io/vertx/core/spi/loadbalancing/Endpoint.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2011-2019 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.spi.loadbalancing; + +/** + * The view of an endpoint by the load balancing policy, it actually wraps an actual endpoint, the main purpose + * of this is to attach metrics to an endpoint that can be managed by the resolver. + * + * @author Julien Viet + * @param the endpoint type + */ +public interface Endpoint { + + /** + * @return the endpoint measured by these metrics. + */ + E endpoint(); + + /** + * @return the metrics for measuring the usage of the endpoint + */ + EndpointMetrics metrics(); + +} diff --git a/src/main/java/io/vertx/core/spi/loadbalancing/EndpointMetrics.java b/src/main/java/io/vertx/core/spi/loadbalancing/EndpointMetrics.java new file mode 100644 index 00000000000..131c87a36db --- /dev/null +++ b/src/main/java/io/vertx/core/spi/loadbalancing/EndpointMetrics.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.spi.loadbalancing; + +/** + * Gather metrics for an endpoint, this interface is write-only and used by the resolver to report + * usage to build statistics for a load balancing algorithm. + * + * @author Julien Viet + */ +public interface EndpointMetrics { + + /** + * Initiate a request + * + * @return the request metric + */ + default M initiateRequest() { + return null; + } + + /** + * Signal the failure of a request/response to the {@code metric} + * @param metric the request metric + * @param failure the failure + */ + default void reportFailure(M metric, Throwable failure) { + } + + /** + * Signal the beginning of the request attached to the {@code metric} + * @param metric the request/response metric + */ + default void reportRequestBegin(M metric) { + } + + /** + * Signal the end of the request attached to the {@code metric} + * @param metric the request/response metric + */ + default void reportRequestEnd(M metric) { + } + + /** + * Signal the beginning of the response attached to the {@code metric} + * @param metric the request/response metric + */ + default void reportResponseBegin(M metric) { + } + + /** + * Signal the end of the response attached to the {@code metric} + * @param metric the request metric + */ + default void reportResponseEnd(M metric) { + } +} diff --git a/src/main/java/io/vertx/core/spi/loadbalancing/EndpointSelector.java b/src/main/java/io/vertx/core/spi/loadbalancing/EndpointSelector.java new file mode 100644 index 00000000000..148bda58738 --- /dev/null +++ b/src/main/java/io/vertx/core/spi/loadbalancing/EndpointSelector.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2011-2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + */ +package io.vertx.core.spi.loadbalancing; + +import java.util.List; + +/** + * Holds the state for performing load balancing. + * + * @author Julien Viet + */ +public interface EndpointSelector { + + /** + * Select an endpoint among the provided list. + * + * @param endpoints the endpoint + * @return the selected index or {@code -1} if selection coul not be achieved + */ + int selectEndpoint(List> endpoints); + + /** + * Create a load balancer endpoint view for the given generic {@code endpoint} + * @param endpoint + * @return + * @param + */ + default Endpoint endpointOf(E endpoint) { + EndpointMetrics metrics = new EndpointMetrics<>() { + }; + return new Endpoint<>() { + @Override + public E endpoint() { + return endpoint; + } + @Override + public EndpointMetrics metrics() { + return metrics; + } + }; + } +} diff --git a/src/main/java/io/vertx/core/spi/loadbalancing/RequestMetric.java b/src/main/java/io/vertx/core/spi/loadbalancing/RequestMetric.java new file mode 100644 index 00000000000..225dd28af99 --- /dev/null +++ b/src/main/java/io/vertx/core/spi/loadbalancing/RequestMetric.java @@ -0,0 +1,11 @@ +package io.vertx.core.spi.loadbalancing; + +class RequestMetric { + + long requestBegin; + long requestEnd; + long responseBegin; + long responseEnd; + Throwable failure; + +} diff --git a/src/main/java/io/vertx/core/spi/logging/LogDelegate.java b/src/main/java/io/vertx/core/spi/logging/LogDelegate.java index c399383430a..fa62f98ffc4 100644 --- a/src/main/java/io/vertx/core/spi/logging/LogDelegate.java +++ b/src/main/java/io/vertx/core/spi/logging/LogDelegate.java @@ -26,50 +26,26 @@ public interface LogDelegate { boolean isTraceEnabled(); - void fatal(Object message); - - void fatal(Object message, Throwable t); - void error(Object message); - void error(Object message, Object... params); - void error(Object message, Throwable t); - void error(Object message, Throwable t, Object... params); - void warn(Object message); - void warn(Object message, Object... params); - void warn(Object message, Throwable t); - void warn(Object message, Throwable t, Object... params); - void info(Object message); - void info(Object message, Object... params); - void info(Object message, Throwable t); - void info(Object message, Throwable t, Object... params); - void debug(Object message); - void debug(Object message, Object... params); - void debug(Object message, Throwable t); - void debug(Object message, Throwable t, Object... params); - void trace(Object message); - void trace(Object message, Object... params); - void trace(Object message, Throwable t); - void trace(Object message, Throwable t, Object... params); - /** * @return the underlying framework logger object, null in the default implementation */ diff --git a/src/main/java/io/vertx/core/spi/metrics/EventBusMetrics.java b/src/main/java/io/vertx/core/spi/metrics/EventBusMetrics.java index 8f92ddaf042..bd34e0186b7 100644 --- a/src/main/java/io/vertx/core/spi/metrics/EventBusMetrics.java +++ b/src/main/java/io/vertx/core/spi/metrics/EventBusMetrics.java @@ -23,13 +23,12 @@ public interface EventBusMetrics extends Metrics { /** * Called when a handler is registered on the event bus.