oak on Deno - How do I build a URL to a route? - deno

I come from a land of ASP.NET Core. Having fun learning a completely new stack.
I'm used to being able to:
name a route "orders"
give it a path like /customer-orders/{id}
register it
use the routing system to build a URL for my named route
An example of (4) might be to pass a routeName and then routeValues which is an object like { id = 193, x = "y" } and the routing system can figure out the URL /customer-orders/193?x=y - notice how it just appends extraneous key-vals as params.
Can I do something like this in oak on Deno?? Thanks.
Update: I am looking into some functions on the underlying regexp tool the routing system uses. It doesn't seem right that this often used feature should be so hard/undiscoverable/inaccessible.
https://github.com/pillarjs/path-to-regexp#compile-reverse-path-to-regexp

I'm not exactly sure what you mean by "building" a URL, but the URL associated to the incoming request is defined by the requesting client, and is available in each middleware callback function's context parameter at context.request.url as an instance of the URL class.
The documentation provides some examples of using a router and the middleware callback functions that are associated to routes in Oak.
Here's an example module which demonstrates accessing the URL-related data in a request:
so-74635313.ts:
import { Application, Router } from "https://deno.land/x/oak#v11.1.0/mod.ts";
const router = new Router({ prefix: "/customer-orders" });
router.get("/:id", async (ctx, next) => {
// An instance of the URL class:
const { url } = ctx.request;
// An instance of the URLSearchParams class:
const { searchParams } = url;
// A string:
const { id } = ctx.params;
const serializableObject = {
id,
// Iterate all the [key, value] entries and collect into an array:
searchParams: [...searchParams.entries()],
// A string representation of the full request URL:
url: url.href,
};
// Respond with the object as JSON data:
ctx.response.body = serializableObject;
ctx.response.type = "application/json";
// Log the object to the console:
console.log(serializableObject);
await next();
});
const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());
function printStartupMessage({ hostname, port, secure }: {
hostname: string;
port: number;
secure?: boolean;
}): void {
if (!hostname || hostname === "0.0.0.0") hostname = "localhost";
const address =
new URL(`http${secure ? "s" : ""}://${hostname}:${port}/`).href;
console.log(`Listening at ${address}`);
console.log("Use ctrl+c to stop");
}
app.addEventListener("listen", printStartupMessage);
await app.listen({ port: 8000 });
In a terminal shell (I'll call it shell A), the program is started:
% deno run --allow-net so-74635313.ts
Listening at http://localhost:8000/
Use ctrl+c to stop
Then, in another shell (I'll call it shell B), a network request is sent to the server at the route described in your question — and the response body (JSON text) is printed below the command:
% curl 'http://localhost:8000/customer-orders/193?x=y'
{"id":"193","searchParams":[["x","y"]],"url":"http://localhost:8000/customer-orders/193?x=y"}
Back in shell A, the output of the console.log statement can be seen:
{
id: "193",
searchParams: [ [ "x", "y" ] ],
url: "http://localhost:8000/customer-orders/193?x=y"
}
ctrl + c is used to send an interrupt signal (SIGINT) to the deno process and stop the server.

I am fortunately working with a React developer today!
Between us, we've found the .url(routeName, ...) method on the Router instance and that does exactly what I need!
Here's the help for it:
/** Generate a URL pathname for a named route, interpolating the optional
* params provided. Also accepts an optional set of options. */
Here's it in use in context:
export const routes = new Router()
.get(
"get-test",
"/test",
handleGetTest,
);
function handleGetTest(context: Context) {
console.log(`The URL for the test route is: ${routes.url("get-test")}`);
}
// The URL for the test route is: /test

Related

Next.js - how to pass a visitor ip into the frontend

I have to send a user IP into the logging service on page load. I use static mode in my next.js app.
I have an idea to use an edge function to get visitor IP, pass it as header and then read this value on the frontend. How can I read it? Is there any other reasonable option to pass information like IP or geo into the frontend?
Thanks!
As I know you can get IP by using getServerSideProps but this approach change page from static to server render more info. You use it inside the page file like this:
export async function getServerSideProps(context) {
return {
props: {}, // will be passed to the page component as props
}
}
context in this case contains user ip you can get it like this:
const ip = context.req.headers["x-forwarded-for"] || context.req.socket.remoteAddress;
and than you can pass it to the frontend or send another request with this ip:
export async function getServerSideProps(context) {
const ip = context.req.headers["x-forwarded-for"] || context.req.socket.remoteAddress;
return {
props: { ip }, // will be passed to the page component as props
}
}
and then catch it and use it in your component:
export default function MyPage({ ip }) {...}
Sending ip from getServerSideProps to the server
you can get ip also on you backend if you using Node.js and express inside req:
const myFn = (req, res, next) => {
const ip = req.headers["x-forwarded-for"] || req.socket.remoteAddress;
}
but if you send request from getServerSideProps there will be your Next.js server ip not user ip. So you need manually add header with ip when you sending request from the getServerSideProps for example with axios (it is better to use interceptors for convenience):
axios.post("/some/route", {...},{
headers: { "client-ip": ctx.req.headers["x-forwarded-for"] || ctx.req.socket.remoteAddress },
});
and than on the Node.js backend:
const myFn = (req, res, next) => {
const ip = req.headers["client-ip"] || req.headers["x-forwarded-for"] || req.socket.remoteAddress;
}
and with all this you can get your user ip in all scenarios.
Note
When you working with localhost you will get ::1 as ip but in production it works as expected.

How to create a VM instance from an instance template within Cloud Functions?

I need to start a Google Compute instance based off a template I've made which has a startup script which downloads the latest game-server executable from my server and runs it. This all works perfectly fine.
Now, my custom built matchmaker will determine if all current games (which are instances of the game server) are full, and if so I want it to run a Cloud Function that creates another new instance from the template I've mentioned above (which basically acts lobby/game for 12 players). Once the instance is created I need the cloud function to return the IP of the newly created instance back to whatever called it (which would be my game).
I know the first part is possible via HTTP POST but I cannot find anywhere in the cloud functions docs/compute docs/admin SDK docs that allows me to create instances and get the IP, is this possible?
EDIT: I have found this documentation but I have not yet found a function to start a VM from a template which then returns the VM's object - which includes it's IP...
You can use directly the APIs. First create the VM, then wait the running state to get the internal and external IP
async function main() {
const auth = new GoogleAuth({
scopes: 'https://www.googleapis.com/auth/cloud-platform'
});
const client = await auth.getClient();
const url = `https://compute.googleapis.com/compute/v1/projects/PROJECT_ID/zones/europe-west1-b/instances`
const template= 'projects/PROJECT_ID/global/instanceTemplates/instance-template-1';
const instanceName = 'example-instance'
const body= '{ "name": "' + instanceName + '" }'
let res = await client.request({ url: url + "?sourceInstanceTemplate=" + template,
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: body
});
res = await client.request({ url: url + "/" + instanceName,
method: 'GET'});
while (res.data['status'] != 'RUNNING') {
setTimeout(function(){},1000)
res = await client.request({ url: url + "/" + instanceName,
method: 'GET'});
}
//Internal Ip (interface0)
console.log(res.data.networkInterfaces[0].networkIP);
//External Ip
console.log(res.data.networkInterfaces[0].accessConfigs[0].natIP);
}
main().catch(console.error);
My NodeJs skill is low (style, format, idioms,...), but this works.

NextJS special characters routes do not work from browser

Using NextJS, I am defining some routes in getStaticPaths by making an API call:
/**
* #dev Fetches the article route and exports the title and id to define the available routes
*/
const getAllArticles = async () => {
const result = await fetch("https://some_api_url");
const articles = await result.json();
return articles.results.map((article) => {
const articleTitle = `${article.title}`;
return {
params: {
title: articleName,
id: `${article.id}`,
},
};
});
};
/**
* #dev Defines the paths available to reach directly
*/
export async function getStaticPaths() {
const paths = await getAllArticles();
return {
paths,
fallback: false,
};
}
Everything works most of the time: I can access most of the articles, Router.push works with all URLs defined.
However, when the article name includes a special character such as &, Router.push keeps working, but copy/pasting the URL that worked from inside the app to another tab returns a page:
An unexpected error has occurred.
In the Network tab of the inspector, a 404 get request error (in Network) appears.
The component code is mostly made of API calls such as:
await API.put(`/set_article/${article.id}`, { object });
With API being defined by axios.
Any idea why it happens and how to make the getStaticPaths work with special characters?
When you transport values in URLs, they need to be URL-encoded. (When you transport values in HTML, they need to be HTML encoded. In JSON, they need to be JSON-encoded. And so on. Any text-based system that can transport structured data has an encoding scheme that you need to apply to data. URLs are not an exception.)
Turn your raw values in your client code
await API.put(`/set_article/${article.id}`)
into encoded ones
await API.put(`/set_article/${encodeURIComponent(article.id)}`)
It might be tempting, but don't pre-encode the values on the server-side. Do this on the client end, at the time you actually use them in a URL.

Loopback 4: Test problems with Sinon and injections

We have trying to do a test with loopback. The test involve to call the google API and we want to mock it with Sinon.
The Controller:
[...]
In the constructor:
#inject('services.OAuth2Service')
private oauth2Service: OAuth2Service
[...]
In the endpoint:
#post('/user-accounts/authenticate/oauth2', {
async authenticateOauth2(
#requestBody() oauthRequest: OAuthId,
#inject(RestBindings.Http.REQUEST) _req: Request,
): Promise<AccessToken> {
const email = await this.oauth2Service.getOAuth2User(oauthRequest); // The method to mock.
....
}
The test:
it('oauth2 authentication with google', async () => {
//Create a spy for the getOAuth2User function
inject.getter('services.OAuth2Service');
var oauth2Service: OAuth2Service;
var setOauthSpy = sinon.spy(oauth2Service, "getOAuth2User"); // Error: Variable 'oauth2Service' is used before being assigned
const res = await client
.post('/user-accounts/authenticate/oauth2')
.set('urlTenant', TEST_TENANT_URL1A)
.set('userType', TEST_USERTYPE1)
.send({
code: TEST_GOOGLE_AUTH2_CODE_KO,
providerId: TEST_GOOGLE_PROVIDER,
redirectUri: TEST_GOOGLE_REDIRECT_URI,
})
.expect(401);
expect(res.body.error.message).to.equal('The credentials are not correct.');
setOauthSpy.restore();
});
How can we test this method? how can we test an endpoint who involves an injection in the constructor in loopback? Please, we need any help.
I see two options:
Before running the test, create a loopback context, bind your stub to "services.OAuth2Service" and use that context to create the controller you want to test.
default value (probably not what you want)
In the place where you use #inject, you provide a default value (and possibly indicate the dependency is optional), e.g. like this:
#inject('services.OAuth2Service', { optional: true })
private oauth2Service: OAuth2Service = mockOAuth2Service,
In other places this might come handy for you, but you should probably not pollute your default production code with defaults to test objects.

Iron Route - Server Side, access this.params from onBeforeAction()

I need to serve some REST API Endpoints from my meteor application.
Endpoints must be accessible on the server side, so I'm using Iron router for server side routing.
All works great, but now I need access to the this.params for permission checking.
My current route:
Router.route('myServerRoute', {
where: "server",
path: '/api/v1/doit/:partner',
onBeforeAction: function(req, res, next) {
API.beforeAction(req, res, next, ['admin','API']);
}
})
The API.beforeAction is a function I'm using to validate the user token (This token is in one of the headers)
This function check if the token is valid and if that user have one of the roles from the 4th parameter.
The :partner is the name of the partner that use the API.
Let say that :partner is 'store1' (/api/v1/doit/store1)
I want to verify that only users that have the store1 role will be able to access the /api/v1/doit/store1 URL
So I want to pass the value of the :partner parameter to the API.beforeAction function
On the onBeforeAction function, I don't have access to the this.params (it is empty)
Some suggested to access the params using Router.current()
But this is a client call, and it is not available server side.
I can use req.url, parse it and get the partner name. but I don't like to do the parsing myself when I know that Iron Route already parsed this URL
Any suggestions how to get the URL parameters inside the onBeforeAction?
You don't need to do permission checking in your onBeforeAction. I implemented my API with Iron Router.
In the example bellow I handle a get request with an API key and return informations or error code.
Router.route('/api/thing/:apikey', { where: 'server' })
.get(function getThing () {
if (typeof this.params.apikey === 'undefined' || this.params.apikey.length != 16 || !Userprofile.findOne({ apiKey: this.params.apikey })) {
this.response.statusCode = 403;
return this.response.end('Not authorized');
}
const things = Thing.find({ owner: Userprofile.findOne({ apiKey: this.params.apikey }).owner }).fetch();
if (things.length > 0) {
this.response.statusCode = 200;
return this.response.end(JSON.stringify(things));
} else {
this.response.statusCode = 200;
return this.response.end('No things found');
}
});

Resources