Vertx HttpClient getNow not working - java

I have problem with vertx HttpClient.
Here's code which shows that tests GET using vertx and plain java.
Vertx vertx = Vertx.vertx();
HttpClientOptions options = new HttpClientOptions()
.setTrustAll(true)
.setSsl(false)
.setDefaultPort(80)
.setProtocolVersion(HttpVersion.HTTP_1_1)
.setLogActivity(true);
HttpClient client = vertx.createHttpClient(options);
client.getNow("google.com", "/", response -> {
System.out.println("Received response with status code " + response.statusCode());
});
System.out.println(getHTML("http://google.com"));
Where getHTML() is from here: How do I do a HTTP GET in Java?
This is my output:
<!doctype html><html... etc <- correct output from plain java
Feb 08, 2017 11:31:21 AM io.vertx.core.http.impl.HttpClientRequestImpl
SEVERE: java.net.UnknownHostException: failed to resolve 'google.com'. Exceeded max queries per resolve 3
But vertx can't connect. What's wrong here? I'm not using any proxy.

For reference: a solution, as described in this question and in tsegismont's comment here, is to set the flag vertx.disableDnsResolver to true:
-Dvertx.disableDnsResolver=true
in order to fall back to the JVM DNS resolver as explained here:
sometimes it can be desirable to use the JVM built-in resolver, the JVM system property -Dvertx.disableDnsResolver=true activates this behavior
I observed this DNS resolution issue with a redis client in a kubernetes environment.

I had this issue, what caused it for me was stale DNS servers being picked up by the Java runtime, i.e. servers registered for a network the machine was no longer connected to. The issue is first in the Sun JNDI implementation, it also exists in Netty which uses JNDI to bootstrap its list of name servers on most platforms, then finally shows up in VertX.
I think a good place to fix this would be in the Netty layer where the set of default DNS servers is bootstrapped. I have raised a ticket with the Netty project so we'll see if they agree with me! Here is the Netty ticket
In the mean time a fairly basic workaround is to filter the default DNS servers detected by Netty, based on whether they are reachable or not. Here is a code Sample in Kotlin to apply before constructing the main VertX instance.
// The default set of name servers provided by JNDI can contain stale entries
// This default set is picked up by Netty and in turn by VertX
// To work around this, we filter for only reachable name servers on startup
val nameServers = DefaultDnsServerAddressStreamProvider.defaultAddressList()
val reachableNameServers = nameServers.stream()
.filter {ns -> ns.address.isReachable(NS_REACHABLE_TIMEOUT)}
.map {ns -> ns.address.hostAddress}
.collect(Collectors.toList())
if (reachableNameServers.size == 0)
throw StartupException("There are no reachable name servers available")
val opts = VertxOptions()
opts.addressResolverOptions.servers = reachableNameServers
// The primary Vertx instance
val vertx = Vertx.vertx(opts)
A little more detail in case it is helpful. I have a company machine, which at some point was connected to the company network by a physical cable. Details of the company's internal name servers were set up by DHCP on the physical interface. Using the wireless interface at home, DNS for the wireless interface gets set to my home DNS while the config for the physical interface is not updated. This is fine since that device is not active, ipconfig /all does not show the internal company DNS servers. However, looking in the registry they are still there:
Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces
They get picked up by the JNDI mechanism, which feeds Netty and in turn VertX. Since they are not reachable from my home location, DNS resolution fails. I can imagine this home/office situation is not unique to me! I don't know whether something similar could occur with multiple virtual interfaces on containers or VMs, it could be worth looking at if you are having problems.

Here is the sample code which works for me.
public class TemplVerticle extends HttpVerticle {
public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
// Create the web client and enable SSL/TLS with a trust store
WebClient client = WebClient.create(vertx,
new WebClientOptions()
.setSsl(true)
.setTrustAll(true)
.setDefaultPort(443)
.setKeepAlive(true)
.setDefaultHost("www.w3schools.com")
);
client.get("www.w3schools.com")
.as(BodyCodec.string())
.send(ar -> {
if (ar.succeeded()) {
HttpResponse<String> response = ar.result();
System.out.println("Got HTTP response body");
System.out.println(response.body().toString());
} else {
ar.cause().printStackTrace();
}
});
}
}

Try using web client instead of httpclient, here you have an example (with rx):
private val client: WebClient = WebClient.create(vertx, WebClientOptions()
.setSsl(true)
.setTrustAll(true)
.setDefaultPort(443)
.setKeepAlive(true)
)
open fun <T> get(uri: String, marshaller: Class<T>): Single<T> {
return client.getAbs(host + uri).rxSend()
.map { extractJson(it, uri, marshaller) }
}
Another option is to use getAbs.

Related

How to programmatically set external ip for Google Cloud virtual machine in Java?

I am trying to programmatically start Google Cloud virtual machine instances. It occurred to me that in order to have internet access, have to set an external IP address.
// Access Config
AccessConfig accessConfig = AccessConfig.newBuilder()
.setNatIP("foo")
.setType("ONE_TO_ONE_NAT")
.setName("External NAT")
.setExternalIpv6("bar")
.build();
// Use the network interface provided in the networkName argument.
NetworkInterface networkInterface = NetworkInterface.newBuilder()
.setName(networkName)
.setAccessConfigs(0, accessConfig)
.build();
That is my status quo. It is inspired by this article post. I hoped that would work in Java, too, but currently, I am stuck.
All I get is:
com.google.api.gax.rpc.InvalidArgumentException: Bad Request
Unfortunately, Google Cloud Compute Engine Docs doesn't really provide any further information, on how to set the external IP properly.
Thanks in advance.
I have encountered the answer. In the Google Cloud Compute Engine Docs it is explained for Windows Instances. It took me a while to recognize it because I've focused only Linux Instances' related questions.
The solution:
instanceResource = Instance.newBuilder()
.setName(instanceName)
.setMachineType(machineType)
.addDisks(disk)
// Add external internet to instance
.addNetworkInterfaces(NetworkInterface.newBuilder()
.addAccessConfigs(AccessConfig.newBuilder()
.setType("ONE_TO_ONE_NAT")
.setName("External NAT")
.build())
.setName("global/networks/default")
.build())
.setMetadata(buildMetadata())
.build();

Schedule message for Azure Service Bus with JMS

I want to send a scheduled message to the Azure Service Bus with JMS.
My code is based on org.apache.qpid.jms.message.JmsMessage. I've found one solution for the given problem, but it uses org.apache.qpid.proton.message.Message which has .getMessageAnnotations(), which allows to edit message annotations and add some properties that are correctly recognized and processed by Azure Service Bus. My message impl is missing that method.
What I've found in offical docs and implementations in node.js, to schedule a message with Azure Service Bus, you need to send header BrokerProperties/brokerProperties which has valid json.
Other headers/properties will be marked as Customer properties and ignored by Azure Service Bus.
official azure docs about JMS says that setting ScheduledEnqueueTimeUtc is not officialy supported by JMS API. But it can be achieved manually by setting property.
So when I send message to the queue, then I can post process it in lambda and set some properties:
jmsTemplate.convertAndSend(queue, payload, message -> {
var date = Date.from(ZonedDateTime.now(ZoneId.of("UTC")).plus(delay, ChronoUnit.MILLIS).toInstant());
var brokerProps = Map.of("ScheduledEnqueueTimeUtc", date.toGMTString());
message.setStringProperty(
"brokerProperties",
objectMapper.writeValueAsString(brokerProps)
);
return message;
});
And it doesn't work. The message arrives at the queue, but when I try to peek it on the Service Bus Explorer on Azure it throws error in the browser console and the operation lasts forever. I guess setting that property brokerProperties does some impact for Service Bus.
I have also tried to send a map with date as a string (with date format that is used by the Azure) like "ScheduledEnqueueTimeUtc", "Thu, 25 Mar 2021 12:54:00 GMT", but it also is recognized as an error by Service Bus (peeking lasts forever and error in the browser console is thrown).
I've tried to set string properties like x-opt-scheduled-enqueue-time or x-ms-scheduled-enqueue-time which I've found in other threads on SO, but none of them works with my example.
I saw that Microsoft gives some library for Java to communicate with Azure Service Bus, but I need to maintain independency from the Cloud provider in my code and don't include any additional libs.
Is there any example of using JMS message implementation from the package org.apache.qpid.jms.message.JmsMessage to set BrokerProperties for Azure Service Bus?
My team is currently facing the same issue.
We found that the ScheduledEnqueueTimeUtc property is set in the MessageAnnotationsMap. Unfortunately, the org.apache.qpid.jms.provider.amqp.message.AmqpJmsMessageFacade, which is used by JMS, has set the getter and setter to package private. But we found out that you can use the setTracingAnnotation(String key, Object value) Method.
Example:
public void sendDelayedMessage() {
final var now = ZonedDateTime.now();
jmsTemplate.send("test-queue", session -> {
final var tenMinutesFromNow = now.plusMinutes(10);
final var textMessage = session.createTextMessage("Hello Service Bus!");
((JmsTextMessage) textMessage).getFacade().setTracingAnnotation("x-opt-scheduled-enqueue-time", Date.from(tenMinutesFromNow.toInstant()));
return textMessage;
});
log.info("Sent at: " + now);
}
Proof:
Big Thanks to my teammate!!

Which region endpoint should AWS Java SDK v2 be using for Route 53?

On Windows 10 I'm using the AWS Java SDK v2 (software.amazon.awssdk:route53:2.8.3) and I'm trying to merely connect and list all my Route 53 hosted zones. I have us-west-1 specified in my user configuration (in my .aws/config file) as the default region.
I create a Route53Client using the following:
Route53Client route53Client = Route53Client.builder().build();
Note that I don't indicate a region, because in the online documentation it says:
When you submit requests using the AWS CLI or SDKs, either leave the Region and endpoint unspecified, or specify us-east-1 as the Region.
I then try to list hosted zones using something like this:
Set<HostedZone> hostedZones = client.listHostedZonesPaginator().stream()
.flatMap(response -> response.hostedZones().stream())
.collect(Collectors.toSet());
In the logs I see a debug message like this:
[DEBUG] Unable to load region from software.amazon.awssdk.regions.providers.SystemSettingsRegionProvider#...:Unable to load region from system settings. Region must be specified either via environment variable (AWS_REGION) or system property (aws.region).
Then it throws a java.net.UnknownHostException for route53.us-west-1.amazonaws.com.
Granted I am on a spotty Internet connection right now. Is that the correct endpoint? If it is, the why isn't that endpoint listed at https://docs.aws.amazon.com/general/latest/gr/rande.html ? If it isn't, why is it trying to connect to a us-west1 endpoint, if I'm following the online documentation (as I quoted above), which indicates that a region need not be indicated? Or is the problem simply my Internet connection and spotty DNS lookup at the moment?
The AWS SDK development team decided to require Route53 requests to explicitly indicate the Region.AWS_GLOBAL or requests would not work, as someone noted in Issue #456 for the SDK:
To access Route53 you would currently need to specify the AWS_GLOBAL region. This was done to prevent customers from using global services and not realizing that for this service your calls are likely not staying in region and could potentially be spanning the globe.
Unfortunately Amazon didn't bother documenting this in the SDK (that I could find), and didn't provide a helpful error message, instead assuming developers would somehow guess the problem when the SDK tried to access an endpoint that did not exist even though the SDK was being used according to the API and according to the online documentation.
In short the Route53 client must be created like this:
route53Client = Route53Client.builder().region(Region.AWS_GLOBAL).build();
Here is the AWS Route 53 V2 Code example that lists hosted zones:
package com.example.route;
//snippet-start:[route.java2.list_zones.import]
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.route53.Route53Client;
import software.amazon.awssdk.services.route53.model.HostedZone;
import software.amazon.awssdk.services.route53.model.Route53Exception;
import software.amazon.awssdk.services.route53.model.ListHostedZonesResponse;
import java.util.List;
//snippet-end:[route.java2.list_zones.import]
public class ListHostedZones {
public static void main(String[] args) {
Region region = Region.AWS_GLOBAL;
Route53Client route53Client = Route53Client.builder()
.region(region)
.build();
listZones(route53Client);
}
//snippet-start:[route.java2.list_zones.main]
public static void listZones(Route53Client route53Client) {
try {
ListHostedZonesResponse zonesResponse = route53Client.listHostedZones();
List<HostedZone> checklist = zonesResponse.hostedZones();
for (HostedZone check: checklist) {
System.out.println("The name is : "+check.name());
}
} catch (Route53Exception e) {
System.err.println(e.getMessage());
System.exit(1);
}
}
//snippet-end:[route.java2.list_zones.main]
}

How to discover the IP address of a Tomcat server on a network?

I have a Android application which consumes a webservice on a local network. There's a config screen where the user inform the server IP address, which is running Apache Tomcat.
I'm looking for a way to auto-detect the server based on the current connected wi-fi network.
i.e: The smartphone's IP is 10.1.1.90 and the server IP is 10.1.1.254.
Is there a way to achieve this? I'm thinking on using ping, but I don't know if is a good ideia.
The way I understand it, you need to discover IP of your tomcat server and connect it using your client.
I am assuming , both the server and client is in your control.
One simple way can be to use jGroups Cluster.
You can make your tomcat discoverable
Client can discover it using the name of the cluster you have provided .Refer the JChannel API that Jgroups uses
I simulated it making following server class
public class TomcatServer {
JChannel channel;
private void start() throws Exception {
channel = new JChannel(); // use the default config, udp.xml
channel.connect("TomcatCluster");
}
public static void main(String[] args) throws Exception {
new TomcatServer().start();
}
}
The simulated client class
public class MobileApp extends ReceiverAdapter {
JChannel channel;
private void start() throws Exception {
channel = new JChannel(); // use the default config, udp.xml
channel.setReceiver(this);
channel.connect("TomcatCluster");
channel.close();
}
public static void main(String args[]) throws Exception {
new MobileApp().start();
}
The client will provide you following information
GMS: address=MACHINENAME-47879, cluster=TomcatCluster, physical address=xxxxx:0:xxx:xxxx:xxxx:xxxx:xxx:xxxx:xxxx
** view: [MACHINENAME-31239|1] [MACHINENAME-31239, MACHINENAME-47879]
Where MACHINENAME-47879 is the client machine and port & MACHINENAME-31239 is the tomcat server name and port
Do you want to detect "a tomcat server" or "your tomcat server" ?
I mean, do you have any way to custom your server ? If it's the case, then you could create a very simple test page on your server (say a "Hello" JSP page), which your Android application could look for.
If your Android gets a "Hello" result with a GET request on http://<tomcat_ip>/hello.jsp, then you may assume that the tomcat is online.
If you can't add this test page, then you can test any page which the server is supposed to serve. (even a 404 page which sometimes is not configured well, and shows the tomcat version...)
Tomcat response headers can contain the xpoweredBy field that would advertise Tomcat if enabled. However it is most often disabled due security considerations, and even disabled by default. You however could re-enable it if you need to auto-detect exactly your Tomcat servers. From the other side, indeed, if you can place a web page on your server, you can simply place a marking page with the agreed signature.
If the server IP is unknown, I would propose the following ways to detect the server on the network:
The most straightforward way is to do the breadcast ping (ping -b broadcast_address where breadcast address can be computed here, for instance). All network devices that are configured so would reply, then verify as explained above which one is the server. However pinging broadcast address requires a rooted phone. Also the router may not support.
Your DHCP service (most likely your router) can often be configured to issue always the same IP address for the same MAC address of your server network card.
If the server is a desktop computer or laptop, it could show its address as QR code on display. It is possible for a smartphone to scan the code from the screen, and this is way easier than to enter IP address through the touchscreen. QR code can also include auto-generated password for extra security.
If there is wireless router with the possible login where both server and client are connected, the internal pages of that router often contain the relevant IP addresses. You would need to implement logging into the router and doing some screen scrapping.
I made an Android app which used a local server in the WLAN. I made the terminal (the phone) broadcast it's own IP address, which the server then picked up.
I used MultiCast class on the phone, which added the ip-address of itself to the payload. The server always has a thread in multicast read class that obains the payload of the packet (which is the terminals ip-address). Set the terminal in datagram read state and send the servers ip-address to terminal.
Maybe are better ways, but a great way to get the ip-addresses of unknown terminals in the network.
The way i had resolved this problem is with the use of enumerations.
public String getLocalIpAddress()
{
try {
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress()) {
return inetAddress.getHostAddress().toString();
}
}
}
} catch (Exception ex) {
}
return null;
}
}

Can I globally set the timeout of HTTP connections?

I have a program that uses javax.xml.ws.Service to call a remote service defined by a WSDL. This program runs on the Google App Engine which, by default, sets the HTTP connection timeout to 5 seconds{1}. I need to increase this timeout value since this service often takes a long time to respond, but since this request is not being made with URLConnection, I cannot figure out how to call URLConnection.setReadTimeout(int){2}, or otherwise change the timeout.
Is there any way to globally set the HTTP connection timeout on the App Engine? And, for purposes of sharing knowledge, how would one go about solving this sort of problem generally?
{1}: https://developers.google.com/appengine/docs/java/urlfetch/overview#Requests
{2}: http://docs.oracle.com/javase/1.5.0/docs/api/java/net/URLConnection.html#setReadTimeout(int)
You could try setting the sun.net.client.defaultConnectTimeout and sun.net.client.defaultReadTimeout system properties documented here, e.g.
System.setProperty("sun.net.client.defaultReadTimeout", "30000");
System.setProperty("sun.net.client.defaultConnectTimeout", "30000");
EDIT
Sorry, just re-read and noticed this is on Google App Engine. I don't know for sure, but given the litigious relationship Google and Oracle have lately, I'm guessing GAE doesn't run the Oracle JVM. I'll leave this here in case someone else runs into a similar problem.
Try this:
Port port = service.getPort(endPointInterface); //or another "getPort(...)"
((BindingProvider) port).getRequestContext()
.put(BindingProviderProperties.REQUEST_TIMEOUT, 30);
See https://developers.google.com/appengine/docs/java/urlfetch/usingjavanet
You can do something like this to get a URLConnection:
URL url = new URL("http://www.example.com/atom.xml");
URLConnection tempConnection = url.openConnection();
tempConnection.setReadTimeout(10);
For App Engine with JAX-WS you have to set the request context (tested today with SDK 1.9.15). For normal machines you cannot go higher than 60s and would have to switch to the bigger machines (Bx) for better use a task queue.
For local testing you would normally use BindingProviderProperties.CONNECT_TIMEOUT and BindingProviderProperties.REQUEST_TIMEOUT, but they are not on the App Engine JRE White List and your code inspection might constantly warn you about that.
The equivalent strings can be used though:
com.sun.xml.internal.ws.connect.timeout
com.sun.xml.internal.ws.connect.timeout
For deployment to App Engine:
com.sun.xml.ws.connect.timeout
com.sun.xml.ws.request.timeout
A full example how to apply that to auto-generated code from JAX-WS 2.x, values have to be provided in milliseconds:
#WebEndpoint(name = "Your.RandomServicePort")
public YourServiceInterface getYourRandomServicePort() {
YourRandomServiceInterface port = super.getPort(YOURRANDOMSERVICE_QNAME_PORT, YourRandomServiceInterface.class);
Map<String, Object> requestContext = ((BindingProvider)port).getRequestContext();
requestContext.put("com.sun.xml.ws.connect.timeout", 10000);
requestContext.put("com.sun.xml.ws.request.timeout", 10000);
return port;
}

Categories

Resources