Nodejs how to pass parameters into an exported route from another route? - express

Suppose I module export "/route1" in route1.js, how would I pass parameters into this route from "/route2" defined in route2.js?
route1.js
module.exports = (app) => {
app.post('/route1', (req,res)=>{
console.log(req.body);
});
}
route2.js
const express = require('express');
const app = express();
//import route1 from route1.js
const r1 = require('./route1')(app);
app.post('/route2', (req, res) => {
//how to pass parameters?
app.use(???, r1) ?
})
In short, route 1 output depends on the input from route 2.

You don't pass parameters from one route to another. Each route is a separate client request. http, by itself, is stateless where each request stands on its own.
If you describe what the actual real-world problem you're trying to solve is, we can help you with some of the various tools there are to use for managing state from one request to the next in http servers. But, we really need to know what the REAL world problem is to know what best to suggest.
The general tools available are:
Set a cookie as part the first response with some data in the cookie. On the next request sent from that client, the prior cookie will be sent with it so the server can see what that data is.
Create a server-side session object (using express-session, probably) and set some data in the session object. In the 2nd request, you can then access the session object to get that previously set data.
Return the data to the client in the first request and have the client send that data back in the 2nd request either in query string or form fields or custom headers. This would be the truly stateless way of doing things on the server. Any required state is kept in the client.
Which option results in the best design depends entirely upon what problem you're actually trying to solve.
FYI, you NEVER embed one route in another like you showed in your question:
app.post('/route2', (req, res) => {
//how to pass parameters?
app.use(???, r1) ?
})
What that would do is to install a new permanent copy of app.use() that's in force for all incoming requests every time your app.post() route was hit. They would accumlate forever.

Related

Second parameter in express route handler

I'm coming across code where there's an additional parameter for express route handlers beyond the path and the callback.
For example:
app.get('/path', authUser, (req,res) => {
...
}
where authUser is a function. Can anybody explain the role of such functions? To date I've only seen express routes with the two parameters.
These are middleware, which are functions that run before your route handler (your third function). You can have as many as these as possible. They basically modify/perform an action based on the request, or maybe manipulate the response.
So this is likely middleware that checks the request for an authenticated user, and will return a 401/403 if not authenticated, meaning that you can write your route handler under the assumption that you are authenticated.
For more info, check out this article
The family of methods such as app.get(), app.post(), app.use(), accept any number of request handlers as successive arguments:
app.get('/path', fn1, fn2, fn3, fn4);
These requests handlers can be used for a variety of purposes. Often times, they are what is generally referred to as middleware which prepares a request for further processing or in some cases blocks a request from further processing. But, they can also be normal request handlers too, they are not just limited to what most people call middleware.
In your specific case:
app.get('/path', authUser, (req,res) => {
...
}
We can guess by the name that authUser is checking to see if the user making the request has been properly authenticated and, if not, then an error status is probably sent as the response and the next request handler in the chain is not called. Or conversely, because authUser has already filtered out any unauthenticated users, the request handler here at the end of the chain can safely assume that the user is already authenticated. So, this particular use is a means of applying middleware to one specific route with no consequences for any other routes defined later.
But, I want to emphasize that this is a generic mechanism that is not limited to just what is classically described as middleware. It can also be used for request handlers that might execute conditionally based on other parameters. For example here's one such example where the first request handler looks the URL and decides to handle the whole request itself based on what it sees in the URL and, if not, passes it on to the next handler:
app.get('/book/:id', (req, res) => {
// check if id is purely numeric
if (/^\d+$/.test(req.params.id)) {
// this is a request for a book by numeric id
// look up the book numeric id in the database and return the meta
// data about this book
} else {
// not a book id, let next request handler have the request
next();
}
}, (req, res) => {
// must be a book title lookup
// look up the book in the title database and return the id
});

Svelte/Sapper fetch/this.fetch is not defined in Server side API

Here is my question about using Svelte/Sapper
I have a file in /src/routes/login.js
According to Sapper doc, it will create an API endpoint in http://<my_domain>/login, which it does
Now in login.js, I would like to call another API server, assume that it is http://another_server/auth
No matter how I use the fetch function: fetch("http://another_server/auth"), this.fetch("http://another_server/auth"), Svelte responses: (this.)fetch is not defined.
The documentation said that it should be run within preload(), so I wrap the fetch under export async function preload(){}, it did not work
So my question is: other than using axios, can I keep on using fetch in server side API request?
Just tested in the template [slug].json.js file, when I add the (this.)fetch function, it did not compile, same error: (this.)fetch is not defined
Thank you very much.
Install the node-fetch npm package and use that in your server routes instead of the default fetch.
(in /src/routes/login.js):
import fetch from 'node-fetch'
...
// inside your route handler
const response = await fetch('http://another_server/auth') // no error
If you are unsure if the code will be executed on the client-side or the server-side (i.e. you're looking for a universal/isomorphic approach) you can use a conditional require:
// inside your route handler
const fetch = process.browser ? window.fetch : require('node-fetch').default
const response = await fetch('http://another_server/auth') // no error
But this is probably overkill in your use case since server routes are by definition executed on the server side.

Soundcloud API /stream endpoint giving 401 error

I'm trying to write a react native app which will stream some tracks from Soundcloud. As a test, I've been playing with the API using python, and I'm able to make requests to resolve the url, pull the playlists/tracks, and everything else I need.
With that said, when making a request to the stream_url of any given track, I get a 401 error.
The current url in question is:
https://api.soundcloud.com/tracks/699691660/stream?client_id=PGBAyVqBYXvDBjeaz3kSsHAMnr1fndq1
I've tried it without the ?client_id..., I have tried replacing the ? with &, I've tried getting another client_id, I've tried it with allow_redirects as both true and false, but nothing seems to work. Any help would be greatly appreciated.
The streamable property of every track is True, so it shouldn't be a permissions issue.
Edit:
After doing a bit of research, I've found a semi-successful workaround. The /stream endpoint of the API is still not working, but if you change your destination endpoint to http://feeds.soundcloud.com/users/soundcloud:users:/sounds.rss, it'll give you an RSS feed that's (mostly) the same as what you'd get by using the tracks or playlists API endpoint.
The link contained therein can be streamed.
Okay, I think I have found a generalized solution that will work for most people. I wish it were easier, but it's the simplest thing I've found yet.
Use API to pull tracks from user. You can use linked_partitioning and the next_href property to gather everything because there's a maximum limit of 200 tracks per call.
Using the data pulled down in the JSON, you can use the permalink_url key to get the same thing you would type into the browser.
Make a request to the permalink_url and access the HTML. You'll need to do some parsing, but the url you'll want will be something to the effect of:
"https://api-v2.soundcloud.com/media/soundcloud:tracks:488625309/c0d9b93d-4a34-4ccf-8e16-7a87cfaa9f79/stream/progressive"
You could probably use a regex to parse this out simply.
Make a request to this url adding ?client_id=... and it'll give you YET ANOTHER url in its return json.
Using the url returned from the previous step, you can link directly to that in the browser, and it'll take you to your track content. I checked on VLC by inputting the link and it streams correctly.
Hopefully this helps some of you out with your developing.
Since I have the same problem, the answer from #Default motivated me to look for a solution. But I did not understand the workaround with the permalink_url in the steps 2 and 3. The easier solution could be:
Fetch for example user track likes using api-v2 endpoint like this:
https://api-v2.soundcloud.com/users/<user_id>/track_likes?client_id=<client_id>
In the response we can finde the needed URL like mentioned from #Default in his answer:
collection: [
{
track: {
media: {
transcodings:[
...
{
url: "https://api-v2.soundcloud.com/media/soundcloud:tracks:713339251/0ab1d60e-e417-4918-b10f-81d572b862dd/stream/progressive"
...
}
]
}
}
...
]
Make request to this URL with client_id as a query param and you get another URL with that you can stream/download the track
Note that the api-v2 is still not public and the request from your client probably will be blocked by CORS.
As mentioned by #user208685 the solution can be a bit simpler by using the SoundCloud API v2:
Obtain the track ID (e.g. using the public API at https://developers.soundcloud.com/docs)
Get JSON from https://api-v2.soundcloud.com/tracks/TRACK_ID?client_id=CLIENT_ID
From JSON parse MP3 progressive stream URL
From stream URL get MP3 file URL
Play media from MP3 file URL
Note: This link is only valid for a limited amount of time and can be regenerated by repeating steps 3. to 5.
Example in node (with node-fetch):
const clientId = 'YOUR_CLIENT_ID';
(async () => {
let response = await fetch(`https://api.soundcloud.com/resolve?url=https://soundcloud.com/d-o-lestrade/gabriel-ananda-maceo-plex-solitary-daze-original-mix&client_id=${clientId}`);
const track = await response.json();
const trackId = track.id;
response = await fetch(`https://api-v2.soundcloud.com/tracks/${trackId}?client_id=${clientId}`);
const trackV2 = await response.json();
const streamUrl = trackV2.media.transcodings.filter(
transcoding => transcoding.format.protocol === 'progressive'
)[0].url;
response = await fetch(`${streamUrl}?client_id=${clientId}`);
const stream = await response.json();
const mp3Url = stream.url;
console.log(mp3Url);
})();
For a similar solution in Python, check this GitHub issue: https://github.com/soundcloud/soundcloud-python/issues/87

How to make CORS API call from Blazor client app with authentication using AutoRest Client?

I am trying to call Web API from Blazor Client App. The API sends required CORS headers and works fine when I call the API using plain Javascript.
The API needs Auth cookies to be included when making a call so using JavaScript I can call:
fetch(uri, { credentials: 'include' })
.then(response => response.json())
.then(data => { console.log(data) })
.catch(error => console.log('Failed'));
Now, I am trying to do the same on Blazor. I came across this section on the docs which says:
requestMessage.Properties[WebAssemblyHttpMessageHandler.FetchArgs] = new
{
credentials = FetchCredentialsOption.Include
};
When I make a call now, it fails with following exception:
WASM: System.Net.Http.HttpRequestException: TypeError: Failed to execute 'fetch' on 'Window': The provided value '2' is not a valid enum value of type RequestCredentials.
I noticed that adding following on Statrup.cs allows me to call API including credentials (here):
if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("WEBASSEMBLY")))
{
WebAssemblyHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.Include;
}
Now, I would like to call the API using AutoRest generated API Client so that I can reuse existing client and save lot of time. Setting DefaultCredentials as above doesn't work and shows following exception:
WASM: Microsoft.Rest.HttpOperationException: Operation returned an invalid status code 'InternalServerError'
Setting the requestMessage.Properties as above, says
The provided value '2' is not a valid enum value of type RequestCredentials`.
I am already injecting HttpClient from Blazor using this technique.
This is not really the answer... I just need space
Setting the requestMessage.Properties as above, says The provided
value '2' is not a valid enum value of type RequestCredentials
If so, what is wrong with the other method I suggested, which I guess is working.
Incidentally,
The provided value '2' is not a valid enum value of type
RequestCredentials
is not related to Blazor, right ? No such type (RequestCredentials) in Blazor. Perhaps your code, whatever it may be, gets the numeric value of the FetchCredentialsOption.Include and not its Enum string
Consider instantiating an HttpRequestMessage object, and configuring it according to your requirements.
Hope this helps...

How do you use Snap's authentication mechanisms during a single POST request?

I'm working on a Haskell Snap-based web app, and I want to expose an API endpoint that will be invoked by a remote service without establishing an authenticated session a-priori; however, I do want that request to be authenticated, so the credentials should be provided at the time of the request.
You could imagine the request containing four fields:
username
password
payload id
payload file
The payload id and file might be irrelevant for this question, but I include them because I (a) need to support file uploads in this request (which, as I understand it, restricts the encoding used to send fields) and (b) need to retrieve at least one non-file field. The combination of those things posed some difficulty when I set this up without authentication, so perhaps it is relevant.
In Snap parlance, let's call this handler uploadHandler.
As indicated above, I have this working fine without authentication, with a setup like this:
uploadHandler :: Handler App App ()
uploadHandler = do
-- collect files / form fields and process as needed.
-- and using the routes:
routes :: [(ByteString, Handler App App ())]
routes = [ ("/login", with auth handleLoginSubmit)
, ("/logout", with auth handleLogout)
, ("/new_user", with auth handleNewUser)
-- handle the upload:
, ("/upload", handleUpload)
]
The naive solution is to simply add 'with auth' and change the type of handleUpload:
uploadHandler :: Handler App (AuthManager App) ()
uploadHandler = do
-- collect files / form fields and process as needed.
-- and using the routes:
routes :: [(ByteString, Handler App App ())]
routes = [ ("/login", with auth handleLoginSubmit)
, ("/logout", with auth handleLogout)
, ("/new_user", with auth handleNewUser)
-- handle the upload, with auth:
, ("/upload", with auth handleUpload)
]
However, this seems to require two requests: (i) authenticate and establish a session, (ii) send the POST request containing the actual payload.
I found a way to do this in one request, but it seems like there should be a more elegant means. Here's the example restricted POST handler I've hacked together:
restrictedPOST :: Handler App (AuthManager App) ()
restrictedPOST = do
mName <- getPostParam "username"
mPass <- getPostParam "password"
let uName = C8.unpack $ fromMaybe "" mName
pass = ClearText $ fromMaybe "" mPass
authResult <- loginByUsername (T.pack uName) pass False
case authResult of
Left authFail -> writeText "Could not log in"
Right user -> writeText (T.append "Hello " (userLogin user))
Is there something like 'with auth' that I can use instead of turning this example (restrictedPOST) into a combinator? I realize it may need to know which fields to get credentials out of, but I also know very little about web services (maybe there is another means? Maybe this is a total non-issue, and I just don't know how to check auth for POST requests. I'm open to any suggestions!)
I don't think you understand what with auth is doing. It has nothing to do with whether authentication is required. All it does is convert a Handler b (AuthManager b) into a Handler b v. No permissions checks are performed. Your restrictedPOST function has the right idea.