I am not able to figure out the correct setup of either my API Gateway or swagger-ui-express.
The lambda function runs successful and and return the html but the related resouces are not able to be loaded and get an 404 error.
const app = express();
const swaggerUI = require('swagger-ui-express');
const serverlessExpress = require('#vendia/serverless-express');
const Stage = process.env.Stage;
const apiId = process.env.apiId;
const options = {
customCss: '.swagger-ui .topbar { display: none }',
customCss: '.swagger-ui .topbar { background-color: red }'
}
let serverlessExpressInstance
async function asyncTask() {
// load latest spec from API Gateway export removed to simplicity reasons.
const swaggerDocument = spec;
console.log("load swagger file complete.");
return swaggerDocument
}
async function setup(event, context) {
const swaggerDocument = await asyncTask()
console.log(swaggerDocument)
app.use('/api-doc', swaggerUI.serveWithOptions({ redirect: false }));
app.use('/api-doc', swaggerUI.setup(swaggerDocument, options));
console.log("setup of swagger complete");
serverlessExpressInstance = serverlessExpress({ app })
return serverlessExpressInstance(event, context)
}
function handler(event, context) {
if (serverlessExpressInstance) return serverlessExpressInstance(event, context)
return setup(event, context)
}
exports.handler = handler
Setup on API Gateway is the following:
Both resources are pointing to the lambda function.
When I load the page via: https://.execute-api..amazonaws.com/dev/api-doc
The following errors are raised:
How can I ensure that the resources are loaded correctly via the correct path ...dev/api/doc/...
You're probbably getting these errors because swagger-ui is configured to look for the image/js resources and express doesn't serve these files. You could potentially use something like serverless-http like this, but I'm not really sure it's a good idea to try and force express into the response of an API gateway lambda function.
As an alternative I managed to get swagger up and running on an API Gateway lambda function, fetching the swagger JSON directly.
import { APIGateway } from "aws-sdk";
import { env } from "../../helpers/env";
const API_ID = env("API_ID", "");
export const handler = async () => {
const apiGateway = new APIGateway({
apiVersion: "2018-11-29",
});
const apiGatewayExport = await apiGateway
.getExport({
exportType: "swagger",
restApiId: API_ID,
stageName: "prod",
accepts: "application/json",
parameters: {
extensions: "apigateway",
},
})
.promise();
if (!apiGatewayExport.body) {
throw new Error("No body found in API Gateway Export");
}
return {
statusCode: 200,
headers: {
"Content-Type": "text/html",
},
body: generateSwaggerPageBody(apiGatewayExport.body?.toString()),
};
};
const generateSwaggerPageBody = (swaggerSpec: string) => `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist#3/swagger-ui.css">
</head>
<body>
<div id="swagger"></div>
<script src="https://unpkg.com/swagger-ui-dist#3/swagger-ui-bundle.js"></script>
<script>
SwaggerUIBundle({
dom_id: '#swagger',
spec: ${swaggerSpec}
});
</script>
</body>
</html>`;
I'm taking the API information directly from API Gateway via SDK and then I just return an html page with tags loading swagger directly - no express overhead needed.
Related
I'm trying to add a custom service worker to read web push notifations from external service. The problem is that my custom service worker "worker.js" is not registered in application.
Next, the code:
WORKER.JS
console.log("Service worker loaded...");
self.addEventListener("install", event => {
console.log("Service worker installed");
});
self.addEventListener("activate", event => {
console.log("Service worker activated");
});
self.addEventListener('message', event => {
console.log(event)
});
self.addEventListener('push', function(e) {
console.log('push event',e)
const data = e.data
self.registration.showNotification(
// data.title,
"hello",
{
// body: data.body,
body:"how are you?",
}
);
})
INDEX.HTML (part)
<head>
...
...
<script>
// The value below is injected by flutter build, do not touch.
var serviceWorkerVersion = null;
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
<!-- MY CUSTOM SERVICE WORKER INITS HERE -->
<script src="./client.js" defer></script>
</head>
<body>
<script>
window.addEventListener('load', function(ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint(
{
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
}
}).then(function(engineInitializer) {
return engineInitializer.initializeEngine();
}).then(function(appRunner) {
return appRunner.runApp();
})
});
</script>
CLIENT.JS
if('serviceWorker' in navigator) {
registerServiceWorker().catch(console.log)
}
async function registerServiceWorker() {
console.log("Before register worker...")
const register = await navigator.serviceWorker.register('./worker.js', {
scope: '/'
});
const PUBLIC_VAPID_KEY = "ApiKeyGeneratedFromService";
function urlBase64ToUint8Array(base64String) {
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
console.log("Before subscription")
const subscription = await register.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey:urlBase64ToUint8Array(PUBLIC_VAPID_KEY)});
console.log("subscription:", subscription.endpoint);
console.log("Before fecth")
await fetch("{UrlFromCustomService}/notifier/webpush/subscription?operation=subscribe", {
method: "POST",
body: JSON.stringify(subscription),
headers: {
"Content-Type": "application/json",
}
})
When I starts application, the "worker.js" get the fetch operation correctly, but the push notification never arrive. Notification permission is also displayed correctly.
When open service worker register...
It's like "flutter_service_worker" replaces my custom worker.
I can only have one service worker at a time ?
Is this the correct way to implement a custom service worker (WITHOUT FIREBASE)?
I'm developing a Vue.js application which has only frontend (no server) and send a lot of requests to different APIs. The originally quite simple app became more complex. And there are problems with some APIs, because browsers do not accept the responses due to CORS. That is why I'm trying to test, if I can migrate the app to Nuxt.js.
My approach is as follows (inspired by this comment), but I expect, that there is probably a better way to send the requests from the client over the server.
pages/test-page.vue
methods: {
async sendRequest(testData) {
const response = await axios.post('api', testData)
// Here can I use the response on the page.
}
}
nuxt.config.js
serverMiddleware: [
{ path: '/api', handler: '~/server-middleware/postRequestHandler.js' }
],
server-middleware/postRequestHandler.js
import axios from 'axios'
const configs = require('../store/config.js')
module.exports = function(req, res, next) {
let body = ''
req.on('data', (data) => {
body += data
})
req.on('end', async () => {
if (req.hasOwnProperty('originalUrl') && req.originalUrl === '/api') {
const parsedBody = JSON.parse(body)
// Send the request from the server.
const response = await axios.post(
configs.state().testUrl,
body
)
req.body = response
}
next()
})
}
middleware/test.js (see: API: The Context)
export default function(context) {
// Universal keys
const { store } = context
// Server-side
if (process.server) {
const { req } = context
store.body = req.body
}
}
pages/api.vue
<template>
{{ body }}
</template>
<script>
export default {
middleware: 'test',
computed: {
body() {
return this.$store.body
}
}
}
</script>
When the user makes an action on the page "test", which will initiate the method "sendRequest()", then the request "axios.post('api', testData)" will result in a response, which contains the HTML code of the page "api". I can then extract the JSON "body" from the HTML.
I find the final step as suboptimal, but I have no idea, how can I send just the JSON and not the whole page. But I suppose, that there must be a much better way to get the data to the client.
There are two possible solutions:
Proxy (see: https://nuxtjs.org/faq/http-proxy)
API (see: https://medium.com/#johnryancottam/running-nuxt-in-parallel-with-express-ffbd1feef83c)
Ad 1. Proxy
The configuration of the proxy can look like this:
nuxt.config.js
module.exports = {
...
modules: [
'#nuxtjs/axios',
'#nuxtjs/proxy'
],
proxy: {
'/proxy/packagist-search/': {
target: 'https://packagist.org',
pathRewrite: {
'^/proxy/packagist-search/': '/search.json?q='
},
changeOrigin: true
}
},
...
}
The request over proxy can look like this:
axios
.get('/proxy/packagist-search/' + this.search.phpLibrary.searchPhrase)
.then((response) => {
console.log(
'Could get the values packagist.org',
response.data
)
}
})
.catch((e) => {
console.log(
'Could not get the values from packagist.org',
e
)
})
Ad 2. API
Select Express as the project’s server-side framework, when creating the new Nuxt.js app.
server/index.js
...
app.post('/api/confluence', confluence.send)
app.use(nuxt.render)
...
server/confluence.js (simplified)
const axios = require('axios')
const config = require('../nuxt.config.js')
exports.send = function(req, res) {
let body = ''
let page = {}
req.on('data', (data) => {
body += data
})
req.on('end', async () => {
const parsedBody = JSON.parse(body)
try {
page = await axios.get(
config.api.confluence.url.api + ...,
config.api.confluence.auth
)
} catch (e) {
console.log('ERROR: ', e)
}
}
res.json({
page
})
}
The request over API can look like this:
this.$axios
.post('api/confluence', postData)
.then((response) => {
console.log('Wiki response: ', response.data)
})
.catch((e) => {
console.log('Could not update the wiki page. ', e)
})
Now with nuxtjs3 :
nuxtjs3 rc release
you have fetch or useFetch no need to import axios or other libs, what is great, automatic parsing of body, automatic detection of head
fetching data
you have middleware and server api on same application, you can add headers on queries, hide for example token etc
server layer
a quick example here in vue file i call server api :
const { status } = await $fetch.raw( '/api/newsletter', { method: "POST", body: this.form.email } )
.then( (response) => ({
status: response.status,
}) )
.catch( (error) => ({
status: error?.response?.status || 500,
}) );
it will call a method on my server, to init the server on root directory i created a folder name server then api, and a file name newsletter.ts (i use typescript)
then in this file :
export default defineEventHandler(async (event) => {
const {REST_API, MAILINGLIST_UNID, MAILINGLIST_TOKEN} = useRuntimeConfig();
const subscriber = await readBody(event);
console.log("url used for rest call" + REST_API);
console.log("token" + MAILINGLIST_TOKEN);
console.log("mailing list unid" + MAILINGLIST_UNID);
let recipientWebDTO = {
email: subscriber,
subscriptions: [{
"mailingListUnid": MAILINGLIST_UNID
}]
};
const {status} = await $fetch.raw(REST_API, {
method: "POST",
body: recipientWebDTO,
headers: {
Authorization: MAILINGLIST_TOKEN,
},
}).then((response) => ({
status: response.status,
}))
.catch((error) => ({
status: error?.response?.status || 500,
}));
event.res.statusCode = status;
return "";
})
What are the benefits ?
REST_API,MAILING_LIST_UNID, MAILING_LIST_TOKEN are not exposed on
client and even file newsletter.ts is not available on debug browser.
You can add log only on server side You event not expose api url to avoid some attacks
You don't have to create a new backend just to hide some criticals token or datas
then it is up to you to choose middleware route or server api. You don't have to import new libs, h3 is embedded via nitro with nuxtjs3 and fetch with vuejs3
for proxy you have also sendProxy offered by h3 : sendProxy H3
When you build in dev server and client build in same time(and nothing to implement or configure in config file), and with build to o, just don deploy your project in static way (but i think you can deploy front in static and server in node i don't know)
I am building a small authentication middleware (because I want to!) but to work the way I wanted I found I was adding routes dynamically to the app, both for the POST back of the username/password as well as the GET for the authentication page script and styles.
Here it is:
function appAuth(auth, options) {
let oneHourMs = (60 * 60 * 1000);
let sessionStore = {};
let html = `<html>
<head>
<link rel="stylesheet" href="style.css" type="text/css">
<script src="index.js" type="module"></script>
</head>
<body>
</body>
</html>`;
return function (request, response, next) {
if (!request.app.authMiddlewarePOST_setup) {
request.app.post(request.path, upload.array(), async function (req, res) {
let data = req.body;
console.log("data", data);
let { username, password } = data;
if (auth(username, password)) {
let now = new Date();
let sessionId = await makeSessionId(now, getRemoteAddr(req));
sessionStore[sessionId] = {
lastTouch: now,
id: sessionId
};
res.cookie("sessionid", sessionId);
res.redirect(302, req,path);
}
else {
res.status(401).send(html);
}
});
request.app.authMiddlewarePOST_setup = true;
}
// Do authenticated session detection
let sessionId = request.cookies.sessionid;
if (sessionId === undefined) {
response.status(401).send(html);
return;
}
let sessionObject = sessionStore[sessionId];
if (sessionObject === undefined
|| isExpired(sessionObject.lastTouch, oneHourMs)) {
response.status(401).send(html);
return;
}
// Otherwise it's all ok.
next();
};
}
I was wondering:
is it ok to add routes like this?
is it better to add a route with an obfuscated path? for example:
app.post("/" + middlewareId + "/" + randomnumber, ...)
if I'm doing this I suppose I should dispose of the route as well?
what would be some alternatives to doing this?
The git repo is over here - I don't want to clutter up the post with even more superflous code.
I have a vue component with methods send out ajax request, I tried done() provided by Mocha to test this method, but it seems do not work correctly by the coverage report(the success and complete methods are not covered according to the report). The codes are as follows:
Vue component code in demo.vue
```
loadData: function(cb) {
var that = this;
$.ajax(
{
url: "/api/nodesWithTask",
async: true,
success: function(data) {
that.nodes = data;
},
complete: function(xhr, status) {
if (cb && typeof (cb) == 'function') {
cb.call();
}
}
}
);
}
```
Test code
```
import Vue from 'vue'
import demo from '#/components/demo'
describe('demo.vue', () => {
var mockData = {"id":"abc"};
var server;
var vm;
before(function () {
server = sinon.fakeServer.create();
//mock response for ajax request
server.respondWith("/api/nodesWithTask",[200,{"Content-Type":"application/json"},JSON.stringify(mockData)]);
const Constructor = Vue.extend(demo);
vm = new Constructor().$mount();
});
after(function () { server.restore(); });
it('load data async', (done) => {
vm.loadData(function(){
done();
});
})
})
```
Thanks for any suggestion in advance.
Advised by my leader, I find one magic config option of sinon fake server as follows
before(function () {
server = sinon.fakeServer.create();
//mock response for ajax request
server.respondWith("/api/nodesWithTask",[200,{"Content-Type":"application/json"},JSON.stringify(mockData)]);
server.respondImmediately = true;
const Constructor = Vue.extend(demo);
vm = new Constructor().$mount();
});
After I set the respondImmediately option to true, the test can run correctly.
I setup GraphQL in Meteor server, and I created a simple API using nimble-restivus package. But I can not call to this API. Please help.
This is API setup:
var Api = new Restivus({
useDefaultAuth: true,
prettyJson: true,
});
Api.addRoute('status', {
post: function() {
return { status: 'OK' };
},
});
This is command line to test api:
curl -X POST http://localhost:3000/api/status
This is config GraphQL on Meteor server:
import { Meteor } from 'meteor/meteor';
import express from 'express';
import {
graphqlExpress,
graphiqlExpress,
} from 'graphql-server-express';
import bodyParser from 'body-parser';
import cors from 'cors';
import { execute, subscribe } from 'graphql';
import { createServer } from 'http';
import { SubscriptionServer } from 'subscriptions-transport-ws';
import { schema } from './schema';
const server = express();
const whitelist = [ 'http://localhost:3000' ];
const corsOptions = {
origin(origin, callback){
const originIsWhitelisted = whitelist.indexOf(origin) !== -1;
callback(null, originIsWhitelisted);
},
credentials: true,
};
server.use(cors(corsOptions));
// server.use('*', cors({ origin: Meteor.settings.GraphQL.clientURL }));
server.use('/graphql', bodyParser.json(), graphqlExpress({ schema }));
server.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
subscriptionsEndpoint: 'ws://localhost:3000/subscriptions',
}));
// We wrap the express server so that we can attach the WebSocket for subscriptions
const ws = createServer(server);
ws.listen(3000, () => {
console.log('GraphQL Server is running');
// Set up the WebSocket for handling GraphQL subscriptions
new SubscriptionServer({
execute,
subscribe,
schema
}, {
server: ws,
path: '/subscriptions',
});
});
This is the result after run command:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot POST /api/status</pre>
</body>
</html>
I fixed it. The reason is Meteor server and GraphQL are running in the same port (port 3000). We need to change port of GraphQL to other port (for example: 3002, etc)