I'd like to add a shutdown route to my Ktor server but I need it to require authentication.
I'm trying to put the shutdown url in my authenticated routes like so:
// application.conf
ktor {
deployment {
port = 8080
host = 127.0.0.1
shutdown.url = "/shutdown"
}
}
// Application.kt
routing {
root()
authenticate("authenticated-routes") {
test1()
test2()
shutdown()
}
}
// Routes.kt
fun Route.shutdown() {
get("/shutdown") {
// shutting down
}
}
But somehow the shutdown route does not require authentication for shutting down the server (something to do with the config overriding the route defined in Routes.kt?)
The docs unfortunately do not give any hints as to how to make the shutdown route authenticated. Any ideas on how I could make sure not just anyone can call the shutdown route and shutdown my server?
The ShutDownUrl plugin has nothing with Routing that's why you can't integrate it with the Authentication plugin. To solve your problem you can manually make an instance of ShutDownUrl class and execute the doShutdown method in a route that may require authentication. Here is an example:
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
fun main() {
val shutdown = ShutDownUrl("") { 1 }
embeddedServer(Netty, port = 3333) {
install(Authentication) {
basic {
realm = "Access for shutting the server down"
validate { credentials ->
if (credentials.name == "jetbrains" && credentials.password == "foobar") {
UserIdPrincipal(credentials.name)
} else {
null
}
}
}
}
routing {
get("/") {
call.respondText { "hello" }
}
authenticate {
get("/shutdown") {
shutdown.doShutdown(call)
}
}
}
}.start()
}
Related
I built a sample Redis based Microservice with NestJS. It's fantastic and works great. In my microservice, after processing the message received over Redis (pub/sub), we publish our result to another Redis channel for a different microservice to pick.
What's the right way to publish? Are there any samples?
For my work, I used Redis package and published it (as opposed to ClientProxyFactory). Works fine and gets the job done.
import {
ClientProxy,
ClientProxyFactory,
Transport,
} from '#nestjs/microservices';
import { Injectable, Logger } from '#nestjs/common';
import * as redis from 'redis';
import { NVResponseDTO } from "../dto/nv.dto";
#Injectable()
export class NVPersistService {
logger = new Logger('NVPersistService');
private client: redis.RedisClient;
constructor() {
this.client = redis.createClient({port: 6379, host: 'localhost'});
this.logger.log('Successfully created client for publish');
}
async publish(result: NVResponseDTO) {
const channel = 'persistence';
try {
await this.client.publish(channel, JSON.stringify(result));
this.logger.log(`Message sent`);
} catch (e) {
this.logger.error(e);
}
}
}
But is this the way to do it or should I use something like below
this.client = ClientProxyFactory.create({
transport: Transport.REDIS,
options: {
url: 'redis://localhost:6379',
}
});
await this.client.connect();
const channel = 'persistence';
const status = await this.client.send<string, NVResponseDTO>(channel, result);
this.logger.log(`Status of send - ${status}`);
Note: Above code did not work for me, hence used Redis directly. Any guidance would be much appreciated
I have created one socket gateway which is working very smoothly with an HTTP request. Now, I am trying to connect socket through https request in NestJs but didn't work for me.
I have also tried to give extra parameters in #WebsocketGateway(5058, { origin : "*:*", secure: true })
I have also checked for NestJs official documentation to work with SSL on the socket but found nothing.
Below is my code which I have created as per documentation.
import { InternalServerErrorException, BadRequestException } from '#nestjs/common';
import { SocketService } from './socket/socket.service';
import { Server, Socket } from 'socket.io';
#WebSocketGateway(5058, { origin : "*:*"} )
export class AppGateway implements OnGatewayConnection, OnGatewayInit {
constructor(private socketService: SocketService) { }
public userIds = [];
afterInit(server: Server) {
console.log("Socket server started");
this.socketService.socket = server;
}
async handleConnection(client) {
try {
console.log(client.id);
this.socketService.socket.to(client.id).emit('status', "connected = " + client.id);
} catch (error) {
throw new InternalServerErrorException(
`Oops.something went wrong, please try again later`,
);
}
}
async handleDisconnect(client) {
this.userIds = this.userIds.filter(user => user.conn_socket_id !== client.id);
}
}
edited:
I can start server and access socket while using an HTTP request, but I am not able to access the socket on HTTPS request.
ex. http://example.com:5058 is working for me,
https://example.com:5058 is not working.
I have fixed it by using a proxy over the socket port so if my socket URL is like https://example.com:5058 then it should be handled from the virtual host and add a proxy to get it working.
The reason for not working is that when you apply HTTPS, it will run on port 443. But now when you are applying an additional port in the URL with HTTPS then it will not run and it will show an error.
Reference for Apache reverse proxy: Link
i'm new to Ktor and i am currently using the quick start http api but i receive the error:
ERROR Application - Unhandled: GET - /snippets
com.fasterxml.jackson.databind.JsonMappingException: kotlin.reflect.jvm.internal.KClassImpl cannot be cast to kotlin.reflect.jvm.internal.KClassImpl (through reference chain: java.util.Collections$Singleton
Map["snippets"]->java.util.ArrayList[0])
CODE:
import com.fasterxml.jackson.databind.SerializationFeature
import io.ktor.application.*
import io.ktor.features.CallLogging
import io.ktor.features.ContentNegotiation
import io.ktor.features.DefaultHeaders
import io.ktor.jackson.jackson
import io.ktor.request.receive
import io.ktor.response.respond
import io.ktor.response.respondText
import io.ktor.routing.Routing
import io.ktor.routing.get
import io.ktor.routing.post
import io.ktor.routing.routing
import java.util.*
data class Snippet(val text: String)
val snippets = Collections.synchronizedList(mutableListOf(
Snippet("hello"),
Snippet("world")
))
fun Application.main() {
install(ContentNegotiation) {
jackson {
enable(SerializationFeature.INDENT_OUTPUT)
}
}
routing {
get("/snippets") {
call.respond(mapOf("snippets" to synchronized(snippets) { snippets.toList() }))
}
}
}
If i use this instead:
call.respond(mapOf("snippets" to synchronized(snippets) { snippets.toString() }))
it returns:
{
"snippets" : "[Snippet(text=hello), Snippet(text=world)]"
}
but now it's using toString() rather than toList(), any idea how i can get it to work as in the quick start using toList()?
Found the issue.
using the watch option in the application.conf file to run the server seems to mess a couple things up.
application.conf file:
ktor {
deployment {
port = 8080
watch = [ / ]
}
application {
modules = [ com.MainKt.main ]
}
}
removing
watch = [ / ]
or switching back to the embedded server seems to have fixed the issue.
fun main() {
embeddedServer(Netty, 8080) {
//rest of the code
}.start(wait = true)
}
I was following this article here (which is not complete unfortunately) in attempt to learn how to friend Ionic 3 based PWA and Firebase Cloud Messaging: Push Notifications with FCM
What I did:
as advised in the article added FCM libraries into service-worker.js:
'use strict';
importScripts('./build/sw-toolbox.js');
importScripts('https://www.gstatic.com/firebasejs/4.9.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/4.9.0/firebase-messaging');
firebase.initializeApp({
// get this from Firebase console, Cloud messaging section
'messagingSenderId': '47286327412'
});
const messaging = firebase.messaging();
messaging.setBackgroundMessageHandler((payload) => {
console.log('Received background message ', payload);
// here you can override some options describing what's in the message;
// however, the actual content will come from the service sending messages
const notificationOptions = {
icon: '/assets/img/appicon.png'
};
return self.registration.showNotification(notificationTitle, notificationOptions);
});
self.toolbox.options.cache = {
name: 'ionic-cache'
};
// pre-cache our key assets
self.toolbox.precache(
[
'./build/main.js',
'./build/vendor.js',
'./build/main.css',
'./build/polyfills.js',
'index.html',
'manifest.json'
]
);
// dynamically cache any other local assets
self.toolbox.router.any('/*', self.toolbox.cacheFirst);
// for any other requests go to the network, cache,
// and then only use that cached resource if your user goes offline
self.toolbox.router.default = self.toolbox.networkFirst;
Then created Firebase Messaging based provider here:
import { Injectable } from "#angular/core";
import * as firebase from 'firebase';
import { Storage } from '#ionic/storage';
#Injectable()
export class FirebaseMessagingProvider {
private messaging: firebase.messaging.Messaging;
private unsubscribeOnTokenRefresh = () => {};
constructor(
private storage: Storage
) {
this.messaging = firebase.messaging();
}
public enableNotifications() {
console.log('Requesting permission...');
return this.messaging.requestPermission().then(() => {
console.log('Permission granted');
// token might change - we need to listen for changes to it and update it
this.setupOnTokenRefresh();
return this.updateToken();
});
}
public disableNotifications() {
this.unsubscribeOnTokenRefresh();
this.unsubscribeOnTokenRefresh = () => {};
return this.storage.set('fcmToken','').then();
}
private updateToken() {
return this.messaging.getToken().then((currentToken) => {
if (currentToken) {
// we've got the token from Firebase, now let's store it in the database
return this.storage.set('fcmToken', currentToken);
} else {
console.log('No Instance ID token available. Request permission to generate one.');
}
});
}
private setupOnTokenRefresh(): void {
this.unsubscribeOnTokenRefresh = this.messaging.onTokenRefresh(() => {
console.log("Token refreshed");
this.storage.set('fcmToken','').then(() => { this.updateToken(); });
});
}
}
And now during app initialization I call enableNotifications() and get error that says that default service worker is not found (404):
A bad HTTP response code (404) was received when fetching the script.
:8100/firebase-messaging-sw.js Failed to load resource: net::ERR_INVALID_RESPONSE
If I move service-worker.js firebase related stuff into default service worker in WWW folder - I get general error from Firebase (Error, failed to register service worker).
QUESTIONS:
- is there a fresh guide on Ionic 3's PWA & FCM?
- at high level what is the difference in registering service workers in Ionic 3 vs Angular? I did watch the tutorial about Angular but can't figure how to do the same in Ionic 3.
UPDATE: the below is valid as of today (02/12/2018) and most likely will be less relevant once AngularFire2 supports messaging module. So take the below with that assumption...
OK I researched and finally made it work on my Ionic 3 PWA, so I am posting solution here:
Prerequisites:
I created ionic blank app (just a home page)
installed angularfire2 and firebase ("angularfire2": "5.0.0-rc.4","firebase": "4.9.1") using npm install, I used specifically 5.0.0-rc.4" cause I had stability issues with latest one;(
created config (filename environment.ts in src folder):
export const firebaseConfig = {
apiKey: "Your Stuff Here from FB",
authDomain: "YOURAPPNAME.firebaseapp.com",
databaseURL: "https://YOURAPPNAME.firebaseio.com",
projectId: "YOURAPPNAME",
storageBucket: "YOURAPPNAME.appspot.com",
messagingSenderId: "FROMFIREBASECONEOLE"
};
I modified app.module.ts to add firebase and angularfire2 this way:
...
import { AngularFireModule } from 'angularfire2';
import 'firebase/messaging'; // only import firebase messaging or as needed;
import { firebaseConfig } from '../environment';
import { FirebaseMessagingProvider } from '../providers/firebase-messaging';
...
#NgModule({
declarations: [
MyApp,
HomePage
],
imports: [
BrowserModule,
IonicModule.forRoot(MyApp),
AngularFireModule.initializeApp(firebaseConfig),
IonicStorageModule.forRoot()
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
HomePage
],
providers: [
FirebaseMessagingProvider,
StatusBar,
SplashScreen,
{provide: ErrorHandler, useClass: IonicErrorHandler}
]
})
export class AppModule {}
Here we also import our provider whose code is below:
in providers folder I created firebase-messaging.ts like this:
import { Injectable } from "#angular/core";
import { FirebaseApp } from 'angularfire2';
// I am importing simple ionic storage (local one), in prod this should be remote storage of some sort.
import { Storage } from '#ionic/storage';
#Injectable()
export class FirebaseMessagingProvider {
private messaging;
private unsubscribeOnTokenRefresh = () => {};
constructor(
private storage: Storage,
private app: FirebaseApp
) {
this.messaging = app.messaging();
navigator.serviceWorker.register('service-worker.js').then((registration) => {
this.messaging.useServiceWorker(registration);
//this.disableNotifications()
this.enableNotifications();
});
}
public enableNotifications() {
console.log('Requesting permission...');
return this.messaging.requestPermission().then(() => {
console.log('Permission granted');
// token might change - we need to listen for changes to it and update it
this.setupOnTokenRefresh();
return this.updateToken();
});
}
public disableNotifications() {
this.unsubscribeOnTokenRefresh();
this.unsubscribeOnTokenRefresh = () => {};
return this.storage.set('fcmToken','').then();
}
private updateToken() {
return this.messaging.getToken().then((currentToken) => {
if (currentToken) {
// we've got the token from Firebase, now let's store it in the database
console.log(currentToken)
return this.storage.set('fcmToken', currentToken);
} else {
console.log('No Instance ID token available. Request permission to generate one.');
}
});
}
private setupOnTokenRefresh(): void {
this.unsubscribeOnTokenRefresh = this.messaging.onTokenRefresh(() => {
console.log("Token refreshed");
this.storage.set('fcmToken','').then(() => { this.updateToken(); });
});
}
}
Please note I init the firebase app and then in constructor we register ionic's default service worker (service-worker.js) that contains the following right after whatever is there by default:
service-worker.js:
// firebase messaging part:
importScripts('https://www.gstatic.com/firebasejs/4.9.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/4.9.0/firebase-messaging.js');
firebase.initializeApp({
// get this from Firebase console, Cloud messaging section
'messagingSenderId': 'YOURIDFROMYOURFIREBASECONSOLE'
});
const messaging = firebase.messaging();
messaging.setBackgroundMessageHandler(function(payload) {
console.log('Received background message ', payload);
// here you can override some options describing what's in the message;
// however, the actual content will come from the Webtask
const notificationOptions = {
icon: '/assets/images/logo-128.png'
};
return self.registration.showNotification(notificationTitle, notificationOptions);
});
At this point you also need to make sure you enabled your app as PWA, there is a good guide from Josh Morony and today there was a video stream on youtube that covers it. In TLDR you need to uncomment this in your index.html:
index.html in src uncomment:
<!-- un-comment this code to enable service worker -->
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js')
.then(() => console.log('service worker installed'))
.catch(err => console.error('Error', err));
}
</script>
OK almost the last thing - your manifest.json (in src) should have exact line:
"gcm_sender_id": "103953800507"
This concludes initial stuff on the client. Please note I didn't implement yet anything to handle notifications while user is in app itself, think for now it just handles when a message is sent from a server while your tab is not in focus (that is what I tested).
Now you want to go to your firebase console and obtain server key (click setting gear icon, then see cloud messaging section there). Copy server key. Also run the client (ionic serve and capture your local token (i just console.logged it). Now try sending yourself the message using a POST method. ( I did it with Postman)
// method: "POST",
//url: "https://fcm.googleapis.com/fcm/send",
// get the key from Firebase console
headers: { Authorization: `key=${fcmServerKey}` },
json: {
"notification": {
"title": "Message title",
"body": "Message body",
"click_action": "URL to your app?"
},
// userData is where your client stored the FCM token for the given user
// it should be read from the database
"to": userData.fcmRegistrationKey
}
So by doing all this I was able to reliable send myself a message WHILE the app was in background. I am yet to handle foreground but this SO question is about how to init default service worker and marry it with FCM.
I hope this will help some learners in future.
I have successfully implemented the process and got success response on API calls. But no notification popup coming on my browser. Any idea?
api: https://fcm.googleapis.com/fcm/send
response got:
{"multicast_id":6904414188195222649,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1545375125056264%e609af1cf9fd7ecd"}]}
cheth the attached url of my console:
I'm building my first app using the release version of Angular 2 (I'm on 2.1.0 currently). I have set up a Route Guard, and I am using it in my routing.ts to secure one of the routes with authentication. When clicked, if not logged in, it redirects the user to the login route. There they can login, and if authenticated, it sets a localStorage token. Here's where I have a problem. I want to then redirect the user to the route they clicked on initially before they were redirected to the login, but I can't figure out how to get the clicked route once they hit the Guard canActivate method, or on the login. This seems like a fairly common usage scenario, but I can't find any examples of doing this.
Ok this is my stripped out example which should illustrate the point:
#Injectable()
export class AuthGuardService implements CanActivate
{
toUrl;
constructor(public authenticationService: AuthenticationService,
public router: Router)
{
}
canActivate(route, state): boolean
{
this.toUrl = state.url; //This is the url where its going
if (this.authenticationService.isLoggedIn()) return true;
this.router.navigate(['/login', {redirect: this.toUrl}]);
}
}
and in the login component use the ngOnInit to check for any redirect ulrs:
export class LoginComponent
{
redirect;
constructor(
private authenticationService: AuthenticationService,
private route: ActivatedRoute,
private router: Router)
{
}
ngOnInit()
{
this.redirect = this.route.snapshot.params['redirect'];
}
logIn(): void
{
this.authenticationService
.login(this.searchParams)
.subscribe(
() =>
{
this.logInSuccess();
},
error =>
{
this.logInFail(error)
})
}
logInSuccess(): void
{
if (this.redirect)
{
this.router.navigateByUrl(this.redirect);
}
else
{
this.router.navigate([''])
}
}
}