How to call a service through a menu-driven application in Ballerina - api

I have a file main.bal which contains the prints the menu and deals with user input. gmail_service.bal file contains a hello service which has the ability to send emails.
main.bal
function main(string... args) {
int c = 0;
while ( c != 2) {
// print options menu to choose from
io:println("-------------------------------------------------------------------------");
io:println("1. Email");
io:println("2. Exit");
io:println("-------------------------------------------------------------------------");
// read user's choice
string choice = io:readln("Enter choice 1 - 2: ");
c = check <int>choice;
if (c == 1) {
//code to send email
}
if (c == 2) {
break;
}
}
}
gmail_service.bal
// A system package containing protocol access constructs
// Package objects referenced with 'http:' in code
import ballerina/http;
import ballerina/io;
import wso2/gmail;
import ballerina/config;
endpoint gmail:Client gmailEP {
clientConfig:{
auth:{
accessToken:config:getAsString("accessToken"),
clientId:config:getAsString("clientId"),
clientSecret:config:getAsString("clientSecret"),
refreshToken:config:getAsString("refreshToken")
}
}
};
documentation {
A service endpoint represents a listener.
}
endpoint http:Listener listener {
port:9090
};
documentation {
A service is a network-accessible API
Advertised on '/hello', port comes from listener endpoint
}
#http:ServiceConfig {
basePath: "/"
}
service<http:Service> hello bind listener {
#http:ResourceConfig {
methods: ["POST"],
path: "/"
}
documentation {
A resource is an invokable API method
Accessible at '/hello/sayHello
'caller' is the client invoking this resource
P{{caller}} Server Connector
P{{request}} Request
}
sayHello (endpoint caller, http:Request request) {
gmail:MessageRequest messageRequest;
messageRequest.recipient = "abc#gmail.com";
messageRequest.sender = "efg#gmail.com";
messageRequest.cc = "";
messageRequest.subject = "Email-Subject";
messageRequest.messageBody = "Email Message Body Text";
//Set the content type of the mail as TEXT_PLAIN or TEXT_HTML.
messageRequest.contentType = gmail:TEXT_PLAIN;
//Send the message.
var sendMessageResponse = gmailEP -> sendMessage("efg#gmail.com", messageRequest);
}
}
How can I invoke the gmail service when the user enters "1"?

In Ballerina, we interact with network-accessible points such as services through endpoints. For example, the your Gmail service source, you have used two endpoints: a listener endpoint and a client endpoint. The listener endpoint is used to bind your hello service to a port and the client endpoint is used to invoke a 3rd party API (the Gmail API).
Similarly, to invoke your hello service from your main() function, you need to create an HTTP client endpoint for the service. You will be interacting with your service through this endpoint. The modified source for main.bal would look something like below. Note that a payload hasn't been set to the POST request since the request body is not used anywhere in the hello service.
import ballerina/http;
import ballerina/io;
endpoint http:Client emailClient {
url: "http://localhost:9090"
};
function main(string... args) {
int c = 0;
while ( c != 2) {
// print options menu to choose from
io:println("-------------------------------------------------------------------------");
io:println("1. Email");
io:println("2. Exit");
io:println("-------------------------------------------------------------------------");
// read user's choice
string choice = io:readln("Enter choice 1 - 2: ");
c = check <int>choice;
if (c == 1) {
http:Response response = check emailClient->post("/", ());
// Do whatever you want with the response
}
if (c == 2) {
break;
}
}
}

Related

Optional route authentication in Ktor

I have a route in my Ktor application that I want to optionally authenticate. What is the best way to go about this? If I put two routing blocks in, both calls default to the unauthenticated one.
e.g.
routing {
post("/my-route") {
val request = call.receive<MyRouteRequest>()
...
}
authenticate(Constants.myAuthScope) {
post("/my-route") {
val request = call.receive<MyRouteRequest>()
val user = call.principal<User>()
...
}
}
It should be possible using more explicit models combined with validation of those either in the route or perhaps in the underlying service (depends if this is seen as domain logic or API logic)
For basic auth it looks a bit like:
sealed interface PrincipalResult {
data class User(/* ... */): PrincipalResult
object NoUserProvided: PrincipalResult
// This might be replaced with a null result to conform with the Ktor API
// I prefer making it explicit and communicate what's going on
// and not just accept a null that means everything and nothing.
//
// This can also be made into a data class and expanded
// with additional information, allowing for better errors and richer debugging
object InvalidUserCredentials: PrincipalResult
}
install(Authentication) {
basic("stuart-auth") {
realm = "Access to the '/' path"
validate { credentials ->
if (credentials.isMissing()) {
PrincipalResult.NoUserProvided
} else if (credentials.isValid() {
PrincipalResult.User(/* ... */)
} else {
PrincipalResult.InvalidUserCredentials
}
}
}
}
now one can do:
authenticate(Constants.myAuthScope) {
post("/my-route") {
val request = call.receive<MyRouteRequest>()
val principalResult = call.principal<PrincipalResult>()
when (principalResult) {
is PrincipalResult.User ->
is PrincipalResult.NoUserProvided ->
is PrincipalResult.InvalidUserCredentials ->
}
// ...
}
}
This pattern should of course be applied to whichever authentication scheme you actually use, such as JWT, OAuth, LDAP etc.

How to return a Flux in async/reactive webclient request with subscribe method

I am using spring hexagonal architecture (port and adapter) as my application need to read the stream of data from the source topic, process/transforms the data, and send it to destination topic.
My application need to do the following actions.
Read the data (which will have the call back url)
Make an http call with the url in the incoming data (using webclient)
Get the a actual data and it needs to be transformed into another format.
Send the transformed data to the outgoing topic.
Here is my code,
public Flux<TargeData> getData(Flux<Message<EventInput>> message)
{
return message
.flatMap(it -> {
Event event = objectMapper.convertValue(it.getPayload(), Event.class);
String eventType = event.getHeader().getEventType();
String callBackURL = "";
if (DISTRIBUTOR.equals(eventType)) {
callBackURL = event.getHeader().getCallbackEnpoint();
WebClient client = WebClient.create();
Flux<NodeInput> nodeInputFlux = client.get()
.uri(callBackURL)
.headers(httpHeaders -> {
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
List<MediaType> acceptTypes = new ArrayList<>();
acceptTypes.add(MediaType.APPLICATION_JSON);
httpHeaders.setAccept(acceptTypes);
})
.exchangeToFlux(response -> {
if (response.statusCode()
.equals(HttpStatus.OK)) {
System.out.println("Response is OK");
return response.bodyToFlux(NodeInput.class);
}
return Flux.empty();
});
nodeInputFlux.subscribe( nodeInput -> {
SourceData source = objectMapper.convertValue(nodeInput, SourceData.class);
// return Flux.fromIterable(this.TransformImpl.transform(source));
});
}
return Flux.empty();
});
}
The commented line in the above code is giving the compilation as subscribe method does not allow return types.
I need a solution "without using block" here.
Please help me here, Thanks in advance.
I think i understood the logic. What do you may want is this:
public Flux<TargeData> getData(Flux<Message<EventInput>> message) {
return message
.flatMap(it -> {
// 1. marshall and unmarshall operations are CPU expensive and could harm event loop
return Mono.fromCallable(() -> objectMapper.convertValue(it.getPayload(), Event.class))
.subscribeOn(Schedulers.parallel());
})
.filter(event -> {
// 2. Moving the if-statement yours to a filter - same behavior
String eventType = event.getHeader().getEventType();
return DISTRIBUTOR.equals(eventType);
})
// Here is the trick 1 - your request below return Flux of SourceData the we will flatten
// into a single Flux<SourceData> instead of Flux<List<SourceData>> with flatMapMany
.flatMap(event -> {
// This WebClient should not be created here. Should be a singleton injected on your class
WebClient client = WebClient.create();
return client.get()
.uri(event.getHeader().getCallbackEnpoint())
.accept(MediaType.APPLICATION_JSON)
.exchangeToFlux(response -> {
if (response.statusCode().equals(HttpStatus.OK)) {
System.out.println("Response is OK");
return response.bodyToFlux(SourceData.class);
}
return Flux.empty();
});
})
// Here is the trick 2 - supposing that transform return a Iterable of TargetData, then you should do this and will have Flux<TargetData>
// and flatten instead of Flux<List<TargetData>>
.flatMapIterable(source -> this.TransformImpl.transform(source));
}

How to dynamically choose which authentication method is used in Ktor?

I implemented google sign-in in my application like so:
fun Application.module(testing: Boolean = false) {
install(CallLogging)
install(ContentNegotiation) {
gson {
setPrettyPrinting()
}
}
val jwtIssuer = environment.config.property("jwt.domain").getString()
val jwtAudience = environment.config.property("jwt.audience").getString()
val jwtRealm = environment.config.property("jwt.realm").getString()
val jwkProvider = JwkProviderBuilder(URL("https://www.googleapis.com/oauth2/v3/certs"))
.cached(10, 24, TimeUnit.HOURS)
.rateLimited(10, 1, TimeUnit.MINUTES)
.build()
install(Authentication) {
jwt {
verifier(jwkProvider) {
withIssuer(jwtIssuer)
withAudience(jwtAudience)
}
realm = jwtRealm
validate { credentials ->
if (credentials.payload.audience.contains(jwtAudience))
JWTPrincipal(credentials.payload)
else
null
}
}
}
routing {
authenticate {
post("/token-sign-in") {
val payload = call.principal<JWTPrincipal>()?.payload ?: error("JWTPrincipal not found")
call.respond(
UserWire(
id = payload.subject,
email = payload.getClaim("email").asString(),
name = payload.getClaim("name").asString(),
profilePictureUrl = payload.getClaim("picture").asString()
)
)
}
}
}
}
I want to authenticate the user every single time they access one of the routes, but I want to have both google and firebase-auth login as an option. The thing is that they require different methods to check the authenticity of the given token, hence I need two authentication methods.
I was thinking of including an "AuthenticationProvider: "Google|Firebase"" in the header of the call, and according to its value, I would decide which authentication method should be called.
So something like this:
fun Application.module(testing: Boolean = false) {
install(Authentication) {
jwt("google") {
// verify google sign in token
}
jwt("firebase") {
// verify firebase token
}
firebaseOrGoogle("firebaseOrGoogle") {
// check header value for auth provider
// verify token with either "firebase" or "google" auth methods
}
}
routing {
authenticate("firebaseOrGoogle") {
post("/token-sign-in") {
// ...
}
get("/transactions") {
// ...
}
}
}
}
Is this at all possible?
If this is possible please could you provide some code as to how to dynamically decide which authentication method should be called?
As an alternative solution, you can configure an authentication feature to try proving the identity of a user by both methods. The first successful check wins. To do that just pass those two configuration names to the authenticate method:
routing {
authenticate("google", "firebase") {
post("/token-sign-in") {
// ...
}
get("/transactions") {
// ...
}
}
}
The order of arguments determines what check comes first.

MassTransit with RabbitMq Request/Response wrong reply address because of network segments

I have a web app that uses a request/response message in Masstransit.
This works on out test environment, no problem.
However on the customer deployment we face a problem. At the customer site we do have two network segments A and B. The component doing the database call is in segment A, the web app and the RabbitMq server in segment B.
Due to security restrictions the component in segment A has to go through a loadbalancer with a given address. The component itself can connect to RabbitMQ with Masstransit. So far so good.
The web component on segment B however uses the direct address for the RabbitMq server. When the web component now is starting the request/response call, I can see that the message arrives at the component in segment A.
However I see that the consumer tries to call the RabbitMQ server on the "wrong" address. It uses the address the web component uses to issue the request. However the component in segment A should reply on the "loadbalancer" address.
Is there a way to configure or tell the RespondAsync call to use the connection address configured for that component?
Of course the easiest would be to have the web component also connect through the loadbalancer, but due to the network segments/security setup the loadbalancer is only reachable from segment A.
Any input/help is appreciated.
I had a similar problem with rabbitmq federation. Here's what I did.
ResponseAddressSendObserver
class ResponseAddressSendObserver : ISendObserver
{
private readonly string _hostUriString;
public ResponseAddressSendObserver(string hostUriString)
{
_hostUriString = hostUriString;
}
public Task PreSend<T>(SendContext<T> context)
where T : class
{
if (context.ResponseAddress != null)
{
// Send relative response address alongside the message
context.Headers.Set("RelativeResponseAddress",
context.ResponseAddress.AbsoluteUri.Substring(_hostUriString.Length));
}
return Task.CompletedTask;
}
...
}
ResponseAddressConsumeFilter
class ResponseAddressConsumeFilter : IFilter<ConsumeContext>
{
private readonly string _hostUriString;
public ResponseAddressConsumeFilter(string hostUriString)
{
_hostUriString = hostUriString;
}
public Task Send(ConsumeContext context, IPipe<ConsumeContext> next)
{
var responseAddressOverride = GetResponseAddress(_hostUriString, context);
return next.Send(new ResponseAddressConsumeContext(responseAddressOverride, context));
}
public void Probe(ProbeContext context){}
private static Uri GetResponseAddress(string host, ConsumeContext context)
{
if (context.ResponseAddress == null)
return context.ResponseAddress;
object relativeResponseAddress;
if (!context.Headers.TryGetHeader("RelativeResponseAddress", out relativeResponseAddress) || !(relativeResponseAddress is string))
throw new InvalidOperationException("Message has ResponseAddress but doen't have RelativeResponseAddress header");
return new Uri(host + relativeResponseAddress);
}
}
ResponseAddressConsumeContext
class ResponseAddressConsumeContext : BaseConsumeContext
{
private readonly ConsumeContext _context;
public ResponseAddressConsumeContext(Uri responseAddressOverride, ConsumeContext context)
: base(context.ReceiveContext)
{
_context = context;
ResponseAddress = responseAddressOverride;
}
public override Uri ResponseAddress { get; }
public override bool TryGetMessage<T>(out ConsumeContext<T> consumeContext)
{
ConsumeContext<T> context;
if (_context.TryGetMessage(out context))
{
// the most hackish part in the whole arrangement
consumeContext = new MessageConsumeContext<T>(this, context.Message);
return true;
}
else
{
consumeContext = null;
return false;
}
}
// all other members just delegate to _context
}
And when configuring the bus
var result = MassTransit.Bus.Factory.CreateUsingRabbitMq(cfg =>
{
var host = cfg.Host(new Uri(hostAddress), h =>
{
h.Username(...);
h.Password(...);
});
cfg.UseFilter(new ResponseAddressConsumeFilter(hostAddress));
...
});
result.ConnectSendObserver(new ResponseAddressSendObserver(hostAddress));
So now relative response addresses are sent with the messages and used on the receiving side.
Using observers to modify anything is not recommended by the documentation, but should be fine in this case.
Maybe three is a better solution, but I haven't found one. HTH

signalr avoid login repeat on load client side

I am using signalR pushnotification service.
I have created a partial view. Inside partial view. Here is my client side code:
<script type="text/javascript">
var objHub;
$(function () {
objHub = $.connection.AnilHub;
loadClientMethods(objHub);
$.connection.hub.start()
.done(function () { objHub.server.connect();
console.log('Now connected, connection ID=' + $.connection.hub.id); }
// at the same time i want to insert into database to set user is online.
objHub.server.login('user1');
)
.fail(function () { console.log('Could not Connect!'); });
function loadClientMethods(objHub) {
objHub.client.getMessages = function (message) {
$('#divMessage').append('<div><p>' + message + '</p></div>');
var height = $('#divMessage')[0].scrollHeight;
$('#divMessage').scrollTop(height);
}
}
</script>
Hub Code
[HubName("MyHub")]
public class MainHub : Hub
{
public void Connect()
{
try
{
string userGroup = "test";
var id = Context.ConnectionId;
Groups.Add(id, userGroup);
Clients.Caller.onConnected(id, userGroup);
}
catch
{
Clients.Caller.NoExistAdmin();
}
}
public void NotifyAllClients(string Message)
{
Clients.Group("test").getMessages(Message);
}
public override Task OnConnected()
{
// Set status online on database
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled = false)
{
// set status disconnct in database
return base.OnDisconnected(stopCalled);
}
}
}
Now I just want to avoid re-loading check of login. because everytime I refresh the page it will call the connect method and call the hub method. How to avoid the re-connect issue. How Do I persist the things, even hub is not handle sessions.
Please suggest...
inside your html, in first load, create a random id and store it in cookies.
In your hub code, create an arraylist and store these random ids with corresponding connection ID.
In your html, try to read the random id from the cookies during each page refresh, if it is not found, it's a new connection, if it is found, use the old random id with a new connection ID to connect to your hub. Then in your hub arraylist, for this particular random id, replace the old connection ID with the new connection ID.