Optional path parameter in vertx - vert.x

I want to use a get request to get optional path parameters in a single end point . For example :-
if user hit below api :-
/metric/monitor/:id
or
/metric/:id
here id is path parameter than i need to route to the same function in a single end point using get request. How can i do the same ?

You can specify a route using regular expressions.
Assuming the id is a number:
router.getWithRegex( "\\/metric(\\/monitor)?\\/(?<ID>\\d+)" ).respond( this::monitor );
and then read the value:
long id = Long.parseLong( ctx.pathParam( "ID" ) );
This is how I usually declare the route using Vert.x Web 4.3.1:
public class MyVerticle extends AbstractVerticle {
#Override
public void start(Promise<Void> startPromise) {
Router router = Router.router( vertx );
BodyHandler bodyHandler = BodyHandler.create();
router.post().handler( bodyHandler );
router.getWithRegex( "\\/metric(\\/monitor)?\\/(?<ID>\\d+)" ).respond( this::monitor );
// Keeping track of it so that I can close it later
this.httpServer = vertx.createHttpServer();
final Future<HttpServer> startHttpServer = httpServer
.requestHandler( router )
.listen( HTTP_PORT )
.onSuccess( v -> LOG.infof( "✅ HTTP server listening on port %s", HTTP_PORT ) );
startHttpServer
.onSuccess( s -> startPromise.complete() )
.onFailure( startPromise::fail );
}
private Future<Result> monitor(RoutingContext ctx) {
long id = Long.parseLong( ctx.pathParam( "ID" ) );
Result result = ... // Do something
return Future.succeededFuture(result);
}
More details about routing with regular expressions are available in the documentation.
But, to be fair, creating two separate entry points seems easier for this case:
router.get( "/metric/monitor/:id" ).respond( this::monitor );
router.get( "/metric/:id" ).respond( this::monitor );

Related

PSQLException "Could not open SSL root certificate file" when attempting SSL connection with Postgres via JDBC

In Java 15, using the PostgreSQL JDBC Driver (ver. 42.2.18, JDBC4.2) from jdbc.postgresql.org with this DataSource:
PGSimpleDataSource ds = new PGSimpleDataSource();
ds.setServerNames( new String[] { "my-server-address" } );
ds.setPortNumbers( new int[] { 25060 } );
ds.setSsl( true );
ds.setDatabaseName( "mydb" );
ds.setUser( "scott" );
ds.setPassword( "tiger" );
this.dataSource = ds;
…the code Connection conn = this.dataSource.getConnection(); throws this exception:
org.postgresql.util.PSQLException: Could not open SSL root certificate file /Users/basilbourque/.postgresql/root.crt.
I know my Postgres 12 server is set up for SSL connections, as my IntelliJ Ultimate has a database access feature (DataGrip) that is successfully connecting with SSL (TLS) protection.
So what is wrong my DataSource configuration in JDBC?
Thanks to this Github issue page for the pgjdbc driver, I found this post by davecramer:
As of 42.2.5 ssl=true implies verify-full as per the release notes. If you wish to get the old behaviour use sslmode=require
Sure enough, replacing ds.setSsl( true ); with ds.setSslMode( "require" ); allowed my JDBC driver make a connection via DataSource.
PGSimpleDataSource ds = new PGSimpleDataSource();
ds.setServerNames( new String[] { "my-server-address" } );
ds.setPortNumbers( new int[] { 25060 } );
ds.setSslMode( "require" ); // Replaces: ds.setSsl( true );
ds.setDatabaseName( "mydb" );
ds.setUser( "scott" );
ds.setPassword( "tiger" );
this.dataSource = ds;
I have no idea what any of these SSL/TLS related options are actually doing, but this worked for me to connect to my DigitalOcean managed Postgres database server.
The following code snippet now runs successfully:
try
(
Connection conn = this.dataSource.getConnection() ;
Statement stmt = conn.createStatement() ;
)
{
String sql =
"""
SELECT uuid_generate_v1()
;
""";
try (
ResultSet rs = stmt.executeQuery( sql ) ;
)
{
while ( rs.next() )
{
UUID uuid = rs.getObject( 1 , UUID.class );
System.out.println( "uuid = " + uuid );
}
}
}
catch ( SQLException e )
{
e.printStackTrace();
}

Can netty establish a connect connection with an https link?

I want to complete a function with netty
http send data to netty -> netty proxy data to target https
I start a client to connet target link.
I tried proxy to http ,it's ok
But when i use https link , future.isSuccess() return false
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof FullHttpRequest) {
FullHttpRequest request = (FullHttpRequest) msg;
Bootstrap b = new Bootstrap();
b.group(ctx.channel().eventLoop())
.channel(ctx.channel().getClass())
.handler(new HttpProxyInitializer(ctx.channel()));
ChannelFuture f = b.connect("https://xxxx",443);
outboundChannel = f.channel();
f.addListener(new ChannelFutureListener() {
#Override
public void operationComplete(ChannelFuture future) {
if (future.isSuccess()) {
future.channel().writeAndFlush(msg);
} else {
ctx.channel().close();
}
}
});
}
}
So is netty connet https not feasible?
How you connect does not look correct.
You would use:
b.connect("hostname", 443)
Ensure your HttpProxyInitializer also add the SslHandler to the ChannelPipeline.
Also if things not work you should inspect the future.cause() as it will contain a Throwable which will provide details about why the operation failed.

Is it possible to use the OpenStack.NET SDK with SoftLayer object storage?

SoftLayer Object Storage is based on the OpenStack Swift object store.
SoftLayer provide SDKs for their object storage in Python, Ruby, Java and PHP, but not in .NET. Searching for .NET SDKs for OpenStack, I came across OpenStack.NET.
Based on this question OpenStack.NET is designed for use with Rackspace by default, but can be made to work with other OpenStack providers using CloudIdentityWithProject and OpenStackIdentityProvider.
SoftLayer provide the following information for connecting to their Object Storage:
Authentication Endpoint
Public: https://mel01.objectstorage.softlayer.net/auth/v1.0/
Private: https://mel01.objectstorage.service.networklayer.com/auth/v1.0/
Username:
SLOS123456-1:email#example.com
API Key (Password):
1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
It's not obvious how this would map to the fields of CloudIdentityWithProject, and OpenStackIdentityProvider but I tried the following and a few other combinations of project name / username / uri:
var cloudIdentity = new CloudIdentityWithProject()
{
ProjectName = "SLOS123456-1",
Username = "email#example.com",
Password = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
};
var identityProvider = new OpenStackIdentityProvider(
new Uri("https://mel01.objectstorage.softlayer.net/auth/v1.0/"),
cloudIdentity);
var token = identityProvider.GetToken(null);
However, in all cases I received the following error:
Unable to authenticate user and retrieve authorized service endpoints
Based on reviewing the source code for SoftLayer's other language libraries and for OpenStack.NET, it looks like SoftLayer's object storage uses V1 auth, while OpenStack.NET is using V2 auth.
Based on this article from SoftLayer and this article from SwiftStack, V1 auth uses a /auth/v1.0/ path (like the one provided by SoftLayer), with X-Auth-User and X-Auth-Key headers as arguments, and with the response contained in headers like the following:
X-Auth-Token-Expires = 83436
X-Auth-Token = AUTH_tk1234567890abcdef1234567890abcdef
X-Storage-Token = AUTH_tk1234567890abcdef1234567890abcdef
X-Storage-Url = https://mel01.objectstorage.softlayer.net/v1/AUTH_12345678-1234-1234-1234-1234567890ab
X-Trans-Id = txbc1234567890abcdef123-1234567890
Connection = keep-alive
Content-Length = 1300
Content-Type = text/html; charset=UTF-8
Date = Wed, 14 Oct 2015 01:19:45 GMT
Whereas V2 auth (identity API V2.0) uses a /v2.0/tokens path, with the request and response in JSON objects in the message body.
Based on the OpenStackIdentityProvider class in OpenStack.NET I hacked together my own SoftLayerOpenStackIdentityProvider like this:
using JSIStudios.SimpleRESTServices.Client;
using net.openstack.Core.Domain;
using net.openstack.Providers.Rackspace;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenStack.Authentication;
using System;
using System.Linq;
using System.Collections.Generic;
namespace OpenStackTest1
{
public class SoftLayerOpenStackIdentityProvider : CloudIdentityProvider
{
public SoftLayerOpenStackIdentityProvider(
Uri urlBase, CloudIdentity defaultIdentity)
: base(defaultIdentity, null, null, urlBase)
{
if (urlBase == null)
throw new ArgumentNullException("urlBase");
}
public override UserAccess GetUserAccess(
CloudIdentity identity, bool forceCacheRefresh = false)
{
identity = identity ?? DefaultIdentity;
Func<UserAccess> refreshCallback =
() =>
{
// Set up request headers.
Dictionary<string, string> headers =
new Dictionary<string, string>();
headers["X-Auth-User"] = identity.Username;
headers["X-Auth-Key"] = identity.APIKey;
// Make the request.
JObject requestBody = null;
var response = ExecuteRESTRequest<JObject>(
identity,
UrlBase,
HttpMethod.GET,
requestBody,
headers: headers,
isTokenRequest: true);
if (response == null || response.Data == null)
return null;
// Get response headers.
string authToken = response.Headers.Single(
h => h.Key == "X-Auth-Token").Value;
string storageUrl = response.Headers.Single(
h => h.Key == "X-Storage-Url").Value;
string tokenExpires = response.Headers.Single(
h => h.Key == "X-Auth-Token-Expires").Value;
// Convert expiry from seconds to a date.
int tokenExpiresSeconds = Int32.Parse(tokenExpires);
DateTimeOffset tokenExpiresDate =
DateTimeOffset.UtcNow.AddSeconds(tokenExpiresSeconds);
// Create UserAccess via JSON deseralization.
UserAccess access = JsonConvert.DeserializeObject<UserAccess>(
String.Format(
"{{ " +
" token: {{ id: '{0}', expires: '{1}' }}, " +
" serviceCatalog: " +
" [ " +
" {{ " +
" endpoints: [ {{ publicUrl: '{2}' }} ], " +
" type: 'object-store', " +
" name: 'swift' " +
" }} " +
" ], " +
" user: {{ }} " +
"}}",
authToken,
tokenExpiresDate,
storageUrl));
if (access == null || access.Token == null)
return null;
return access;
};
string key = string.Format("{0}:{1}", UrlBase, identity.Username);
var userAccess = TokenCache.Get(key, refreshCallback, forceCacheRefresh);
return userAccess;
}
protected override string LookupServiceTypeKey(IServiceType serviceType)
{
return serviceType.Type;
}
}
}
Because some of the members of UserAccess (like IdentityToken and Endpoint) have no way to set their fields (the objects have only a default constructor and only read-only members), I had to create the UserAccess object by deserializing some temporary JSON in a similar format as returned by the V2 API.
This works, ie I can now connect like this:
var cloudIdentity = new CloudIdentity()
{
Username = "SLOS123456-1:email#example.com",
APIKey = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
};
var identityProvider = new SoftLayerOpenStackIdentityProvider(
new Uri("https://mel01.objectstorage.softlayer.net/auth/v1.0/"),
cloudIdentity);
var token = identityProvider.GetToken(null);
And then get access to files etc like this:
var cloudFilesProvider = new CloudFilesProvider(identityProvider);
var containers = cloudFilesProvider.ListContainers();
var stream = new MemoryStream();
cloudFilesProvider.GetObject("testcontainer", "testfile.dat", stream);
However, is there a better way than this to use SoftLayer Object Storage from .NET?
I briefly also looked at the OpenStack SDK for .NET (a different library to OpenStack.NET), but it too seems to be based on V2 auth.

SSL Endpoint with Heroku and 303

I have created an SSL Endpoint with heroku. I have a test environment and a live environment. I have a REST call that generates a 303. Since Heroku handles the SSL in it's router, I'm not sure how I can detect if my SEE OTHER URL should create an HTTP or HTTPS based URI. Here's some sample code:
#GET
#Path( "/job/{jobId}" )
public Response getCallStatus( #PathParam( "jobId" ) Long jobId, #Context UriInfo uriInfo ) throws Exception {
if ( !jobService.isDone( jobId ) )
return build( Response.ok( POLLING_FREQUENCY ) );
URI jobLocation = uriInfo.getAbsolutePathBuilder().path( "result" ).build();
return build( Response.seeOther( jobLocation ) );
}
Because my server isn't handling the SSL (heroku is) the absolute path for the REST call will use HTTP instead of HTTPS. If I hard code HTTPS I will break my unit tests or other environment that do not require the HTTPS protocol.
Any thoughts? Or am I misunderstanding how heroku is doing this?
Okay, so here's the answer. Heroku does NOT forward the request as HTTPS. Because of this you need to look into the x-fowarded-proto header to decide what the 303 location is that you should send back to your client. The above code sample would change to something like this:
#GET
#Path( "/job/{jobId}" )
public Response getCallStatus( #PathParam( "jobId" ) Long jobId, #Context UriInfo uriInfo, #Context HttpHeaders headers ) throws Exception {
if ( !jobService.isDone( jobId ) )
return build( Response.ok( POLLING_FREQUENCY ) );
UriBuilder builder = uriInfo.getAbsolutePathBuilder().path( "result" );
String scheme = headers.getHeaderString( "x-forwarded-proto" );
if ( scheme != null )
builder.scheme( scheme );
return build( Response.seeOther( builder.build() ) );
}
That's basically it.
But a better way to handle it that would not require ANY changes in the coded REST METHOD would be to add a container request filter like this:
#PreMatching
public class HerokuContainerRequestFilter implements ContainerRequestFilter {
#Override
public void filter( ContainerRequestContext ctx ) throws IOException {
List<String> schemes = ctx.getHeaders().get( "x-forwarded-proto" );
if ( schemes != null && !schemes.isEmpty() ) {
String scheme = schemes.get( 0 );
UriBuilder builder = ctx.getUriInfo().getRequestUriBuilder();
ctx.setRequestUri( builder.scheme( scheme ).build() );
}
}
}
Then you just register this filter with your RestConfig like this:
public class RestApplication extends ResourceConfig {
public RestApplication() {
packages( "com.myapp.rest.service" );
// along with any other providers, etc that you register
register( HerokuContainerRequestFilter.class );
}
}
While user1888440 answer is totally working I'd rather configure https forwarding at server level.
For example, if you are using an embedded jetty as you're heroku web server you could use jetty built-in org.eclipse.jetty.server.ForwardedRequestCustomizer :
Customize Requests for Proxy Forwarding.
This customizer looks at at HTTP request for headers that indicate it has been forwarded by one or more proxies. Specifically handled are:
X-Forwarded-Host
X-Forwarded-Server
X-Forwarded-For
X-Forwarded-Proto
If these headers are present, then the Request object is updated so that the proxy is not seen as the other end point of the connection on which the request came
So instead of starting your server with :
Server server = new Server(port);
You could use :
Server server = new Server();
HttpConfiguration httpConfiguration = new HttpConfiguration();
httpConfiguration.addCustomizer(new ForwardedRequestCustomizer());
ServerConnector serverConnector = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration));
serverConnector.setPort(port);
server.addConnector(serverConnector);

Send certificate file with Scala Dispatch

I need to be able to send a certificate file (.pem, I think), with a get request using scala and dispatch.
How do you do that?
Based on the Java code in #sbridges sample, I came up with the following Scala code using dispatch. It creates a custom SSL context containing the certificates you provide (and only those; the default store of trusted root certificates is not used by this code when verifying the remote host).
class SslAuthenticatingHttp(certData: SslCertificateData) extends Http {
override val client = new AsyncHttpClient(
(new AsyncHttpClientConfig.Builder).setSSLContext(buildSslContext(certData)).build
)
private def buildSslContext(certData: SslCertificateData): SSLContext = {
import certData._
val clientCertStore = loadKeyStore(clientCertificateData, clientCertificatePassword)
val rootCertStore = loadKeyStore(rootCertificateData, rootCertificatePassword)
val keyManagerFactory = KeyManagerFactory.getInstance("SunX509")
keyManagerFactory.init(clientCertStore, clientCertificatePassword.toCharArray)
val keyManagers = keyManagerFactory.getKeyManagers()
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustManagerFactory.init(rootCertStore)
val trustManagers = trustManagerFactory.getTrustManagers()
val context = SSLContext.getInstance("TLS")
context.init(keyManagers, trustManagers, null)
context
}
private def loadKeyStore(keyStoreData: Array[Byte], password: String): KeyStore = {
val store = KeyStore.getInstance(KeyStore.getDefaultType)
store.load(new ByteArrayInputStream(keyStoreData), password.toCharArray)
store
}
}
case class SslCertificateData (
clientCertificateData: Array[Byte],
clientCertificatePassword: String,
rootCertificateData: Array[Byte],
rootCertificatePassword: String)
which would be used as in:
val certificateData = SslCertificateData(/* bytes from .jks file for client cert here */, "secret!",
/* bytes from .jks file for root cert here */, "also secret!")
val http = new SslAuthenticatingHttp(certificateData)
val page = http(req OK as.String)
println(page())
Note that this keeps the certificate data in memory, which is not the most secure way to do it and consumes memory unnecessarily. It may in many cases be more suitable to store an InputStream or a filename in the SslCertificateData case class.
I am assuming you want to do https with client certificates. I think this needs to be set up at the jvm level, there is a good explanation here how to do it.
There seems to be a way to do this with ning directly, as explained here,
the code is copied below,
// read in PEM file and parse with commons-ssl PKCS8Key
// (ca.juliusdavies:not-yet-commons-ssl:0.3.11)
RandomAccessFile in = null;
byte[] b = new byte[(int) certFile.length()];
in = new RandomAccessFile( certFile, "r" );
in.readFully( b );
char[] password = hints.get( "password" ).toString().toCharArray();
PKCS8Key key = new PKCS8Key( b, password );
// create empty key store
store = KeyStore.getInstance( KeyStore.getDefaultType() );
store.load( null, password );
// cert chain is not important if you override the default KeyManager and/or
// TrustManager implementation, IIRC
store.setKeyEntry( alias, key.getPrivateKey(), password, new DefaultCertificate[0] );
// initialize key and trust managers -> default behavior
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance( "SunX509" );
// password for key and store have to be the same IIRC
keyManagerFactory.init( store, password );
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
TrustManagerFactory tmf = TrustManagerFactory.getInstance( TrustManagerFactory.getDefaultAlgorithm() );
tmf.init( store );
TrustManager[] trustManagers = tmf.getTrustManagers();
// override key and trust managers with desired behavior - for example
// * 'trust everything the server gives us' -> TrustManager#checkServerTrusted
// * 'always return a preset alias to use for auth' -> X509ExtendedKeyManager#chooseClientAlias, X509ExtendedKeyManager#chooseEngineClientAlias
for ( int i = 0; i < keyManagers.length; i++ )
{
if ( keyManagers[i] instanceof X509ExtendedKeyManager )
{
AHCKeyManager ahcKeyManager = new AHCKeyManager( (X509ExtendedKeyManager) keyManagers[i] );
keyManagers[i] = ahcKeyManager;
}
}
for ( int i = 0; i < trustManagers.length; i++ )
{
if ( tm instanceof X509TrustManager )
{
AHCTrustManager ahcTrustManager = new AHCTrustManager( manager, (X509TrustManager) trustManagers[i] );
trustManagers[i] = ahcTrustManager;
}
}
// construct SSLContext and feed to AHC config
SSLContext context = SSLContext.getInstance( "TLS" );
context.init( keyManagers, trustManagers, null );
ahcCfgBuilder.setSSLContext(context);