thelinmichael / spotify-web-api-node

A Node.js wrapper for Spotify's Web API.
http://thelinmichael.github.io/spotify-web-api-node/
MIT License
3.11k stars 497 forks source link

Bad Request when executed several times, error 400 #271

Open carlosmrtll opened 5 years ago

carlosmrtll commented 5 years ago

I'm building a page where, after clicking a button, it fetches a bunch of data from Spotify. Initially, it all works great. But if this button is clicked 3 or more times without restarting the server, I get the following error:

{ [WebapiError: Bad Request] name: 'WebapiError', message: 'Bad Request', statusCode: 400 }

And I don't mean clicking the button rapidly, but rather just clicking it several times normally after it's done executing each time. Bellow I include very simple code for replicating this problem, with the following structure:

.
├── .env
├── index.js
├── package.json
└── views
    ├── error.html
    └── index.html

Basically, it first obtains some general info about the user. Then it gets the most recent saved songs, most recent played songs and last month's top songs for that user. For all these songs, then it fetches their respective audio features:
index.js

require('dotenv').config();
const SpotifyWebApi = require('spotify-web-api-node');
const express = require('express');
const url = require('url').URL;

const scopes = ['user-library-read', 'user-read-recently-played', 'user-top-read'];
const state = 'some-state-of-my-choice';
const spotifyApi = new SpotifyWebApi({
  clientId: process.env.SPOTIFY_CLIENT_ID,
  clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
  redirectUri: process.env.SPOTIFY_REDIRECT_URI
});
const authorizeURL = spotifyApi.createAuthorizeURL(scopes, state);

const app = express();
app.set('port', process.env.PORT || 3000);
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
app.use(express.static(__dirname + '/views'));

let saved_songs_ID = [];
let played_songs_ID = [];
let top_songs_ID = [];
function obtain_results(success, fail) {
  let results = {};
  // get user info
  spotifyApi.getMe().then(function(data) {
    results['display_name'] = data.body['display_name'];
    results['followers'] = data.body['followers']['total'];
    return spotifyApi.getMySavedTracks({limit:50});
  }, function(err) { console.error(err); fail(); })

  // get user's recently SAVED tracks
  .then(function(data) {
    console.log("gathering recently SAVED...");
    let items = data.body['items'];
    results['most_recent_saved_song'] = items[0]['track']['name'];
    for(let i=0; i<items.length; i++) {
      saved_songs_ID.push(items[i]['track']['id']);
    }
    return spotifyApi.getMyRecentlyPlayedTracks({limit:50});
  }, function(err) { console.error(err); fail(); })

  // get user's recently PLAYED tracks
  .then(function(data) {
    console.log("gathering recently PLAYED...");
    let items = data.body['items'];
    results['most_recent_played_song'] = items[0]['track']['name'];
    for(let i=0; i<items.length; i++){
      played_songs_ID.push(items[i]['track']['id']);
    }
    return spotifyApi.getMyTopTracks({limit:50, time_range:'short_term'})
  }, function(err) { console.error(err); fail(); })

  // get user's TOP songs
  .then(function(data) {
    console.log("gathering last month's TOP...");
    let items = data.body['items'];
    results['top_song'] = items[0]['name'];
    for(let i=0; i<items.length; i++){
      top_songs_ID.push(items[i]['id']);
    }
    return spotifyApi.getAudioFeaturesForTracks(saved_songs_ID);
  }, function(err) { console.error(err); fail(); })

  // get FEATURES for user's SAVED songs
  .then(function(data) {
    console.log("gathering FEATURES for SAVED tracks...");
    console.log(data.body['audio_features'][0]['danceability']);
    return spotifyApi.getAudioFeaturesForTracks(played_songs_ID);
  }, function(err) { console.error(err); fail(); })

  // get FEATURES for user's PLAYED songs
  .then(function(data) {
    console.log("gathering FEATURES for PLAYED tracks...");
    console.log(data.body['audio_features'][0]['danceability']);
    return spotifyApi.getAudioFeaturesForTracks(top_songs_ID);
  }, function(err) { console.error(err); fail(); })

  // get FEATURES for user's TOP songs
  .then(function(data) {
    console.log("gathering FEATURES for TOP tracks...");
    console.log(data.body['audio_features'][0]['danceability']);
    success(results);
  }, function(err) { console.error(err); fail(); })
}

app.get('/',function(req, res){
  res.render('index.html');
});

app.post('/',function(req, res){
  res.redirect(authorizeURL);
});

app.get('/home',function(req, res){
  const current_url = new url('localhost:' + process.env.PORT + req.url);
  const code = current_url.searchParams.get('code');

  // Retrieve an access token and a refresh token
  spotifyApi.authorizationCodeGrant(code).then(
    function(data) {
      // Set the access token on the API object to use it in later calls
      spotifyApi.setAccessToken(data.body['access_token']);
      spotifyApi.setRefreshToken(data.body['refresh_token']);

      console.log('-------------------------------------------------------------');
      console.log('\nThe access token is ' + data.body['access_token'] + '\n');
      console.log('It expires in ' + data.body['expires_in'] + '\n');
      console.log('The refresh token is ' + data.body['refresh_token'] + '\n');

      obtain_results(
        function(results){
          console.log("all data gathered successfully");
          console.log(results);
          res.redirect('/');
        },
        function(){ res.render('error.html'); }
      );
    },
    function(err) { console.log('Something went wrong!'); }
  );
});

app.listen(app.get('port'), function(){
  console.log('started on http://localhost:' + app.get('port') + ' press Ctrl-C to terminate');
});

package.json

{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "dotenv": "^7.0.0",
    "ejs": "^2.6.1",
    "express": "^4.16.4",
    "spotify-web-api-node": "^4.0.0"
  }
}

.env

SPOTIFY_CLIENT_ID=your-client-id
SPOTIFY_CLIENT_SECRET=your-client-secret
SPOTIFY_REDIRECT_URI=your-redirect-url
PORT=3000

index.html

<!DOCTYPE html>
<html>
<head>
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>
<body>
    <br><br>
    <center>
        <form method="post">
            <input class="btn btn-primary" type="submit" value="BEGIN">
        </form>
    </center>
</body>
</html>

error.html

<!DOCTYPE html>
<html>
<head>
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
</head>
<body>
    <br><br>
    <center>
        <h3>Something went wrong.</h3>
    </center>
</body>
</html>

To replicate this yourself, just do

npm install
npm start
carlosmrtll commented 5 years ago

I want this website to be online and used by many different people, or even the same person multiple times. So I really need to solve this problem, otherwise the whole system stops working after just 3 clicks on this button.

tigar commented 5 years ago

@carlosmartell97 I started to do some digging on this and replicated your error, but I also got this: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

So at least we know you're overlapping requests, trying to set headers while still awaiting a response. I can look a bit more about where that's happening but hopefully that's enough to get you started. I'm sure there'll be others having the same issues with other node projects, this doesn't look to be specific to the wrapper.

mikirobles commented 5 years ago

+1

This happened to a productive application of mine, hundreds of users couldn't login because of an inconsistent 400 Bad Request error on Authorization Code grant.

To fix this, I just had to implement login from scratch without the spotify-web-api-node library helper.