12

I made a web-app using AngularJs where user can upload .txt files to a server using ng-file-upload.

Now I wanted a simple Node.js server to test the upload part and watch how progress bars and error messages in the page behave, but having a very poor knowledge about how Node.js and the entire backend thing work, I tried to use the Node.js server provided by ng-file-upload's very wiki.

I tried to make some changes that brought me to this app.js file:

var http = require('http')
  , util = require('util')
  , multiparty = require('multiparty')
  , PORT = process.env.PORT || 27372

var server = http.createServer(function(req, res) {
  if (req.url === '/') {
    res.writeHead(200, {'content-type': 'text/html'});
    res.end(
      '<form action="/upload" enctype="multipart/form-data" method="post">'+
      '<input type="text" name="title"><br>'+
      '<input type="file" name="upload" multiple="multiple"><br>'+
      '<input type="submit" value="Upload">'+
      '</form>'
    );
  } else if (req.url === '/upload') {
    var form = new multiparty.Form();

    form.parse(req, function(err, fields, files) {
      if (err) {
        res.writeHead(400, {'content-type': 'text/plain'});
        res.end("invalid request: " + err.message);
        return;
      }
      res.writeHead(200, {'content-type': 'text/plain'});
      res.write('received fields:\n\n '+util.inspect(fields));
      res.write('\n\n');
      res.end('received files:\n\n '+util.inspect(files));
    });
  } else {
    res.writeHead(404, {'content-type': 'text/plain'});
    res.end('404');
  }
});
server.listen(PORT, function() {
  console.info('listening on http://127.0.0.1:'+PORT+'/');
});

and the UserController.js is simple as this

UserController = function() {};

UserController.prototype.uploadFile = function(req, res) {
    // We are able to access req.files.file thanks to 
    // the multiparty middleware
    var file = req.files.file;
    console.log(file.name);
    console.log(file.type);
}

module.exports = new UserController();

Inside a directive's controller in my AngularJs app I use the ng-file-upload upload service in this way

var upload = Upload.upload({
    url: 'http://127.0.0.1:27372/upload',
    method: 'POST',
    fields: newFields,
    file: newFile  
    }).progress(function (evt) {
        $scope.progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
    }).success(function (data, status, headers, config) {
        console.log("OK");
    }).error(function(data, status, headers, config) {
        console.log("KO");
});

Finally, I start the server like so:

node app.js

and all looks fine:

listening on http://127.0.0.1:27372

With all that being said, when I launch the AngularJs web-app and try to upload a file I get the following error

OPTIONS http://127.0.0.1:27372/upload 400 (Bad Request)                                   angular.js:10514
XMLHttpRequest cannot load http://127.0.0.1:27372/upload. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:9000' is therefore not allowed access. The response had HTTP status code 400.                   (index):1

After some googling I found many gists used to allow CORS requests like this one, but my Node.js knowledge is so poor I don't even know where I should place those lines of code.

Furthermore, I tried to get a console.log(err) within the app.js form.parse part itself and got this printed on the terminal:

DEBUG SERVER: err =
{ [Error: missing content-type header] status: 415, statusCode: 415 }

What am I missing and what can I do to get this simple Node.js server working?


EDIT 29/07/2015

I chosed to follow the first option suggested by @Can Guney Aksakalli, because it's the only one I can do, but even if now the code looks like this:

var server = http.createServer(function(req, res) {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
  if (req.url === '/') {
    res.writeHead(200, {'Content-type': 'text/html'});
// and the code stays the same

This solution it's not working; I keep getting the same error message in both the Chrome console and the terminal from which I called node app.js, as I wrote in the last part of my initial question.

Gargaroz
  • 313
  • 9
  • 28

2 Answers2

7

You are serving html files on http://localhost:9000 and NodeJS application on http://localhost:27372; therefore you have CORS issue. (This issue is not related to angularjs though). You have to either enable CORS for NodeJS or serve your all application in the same domain.

Possible solutions:

1- Enabling CORS in NodeJS server

You can enable CORS in your server side by specifying allowed origins in response header. These lines would enable requests to your application from all domains. (add this to beginning of the function definition.)

var server = http.createServer(function(req, res) {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');

  // the rest of the method ...

}

Enabling CORS for all domain is not always a good decision, please also check this.

2- Serving your html files from NodeJS application

Here with following additions you would serve your html files from NodeJS server. (You don't need to use the other server anymore.)

var serveStatic = require('serve-static');
var finalhandler = require('finalhandler');

//...

var serve = serveStatic('./path/to/your/static/folder');

var server = http.createServer(function(req, res) {

  //...

  var done = finalhandler(req, res);
  serve(req, res, done);
});

I would also recommend you to use ExpressJS for richer server capabilities instead of vanilla node.js http server.

3- Providing a proxy connection from your html files server to nodejs app

I don't know what you are using as a server for static html files but it is possible to have a proxy between your static server to NodeJS application server.


EDIT 1

Here is a basic implementation for option 2- Serving your html files from NodeJS application.

In this example I used ExpressJS. Client side static files are served in public folder, for post request to /api/upload url would upload the file. Here is the server code app.js:

var express = require('express'),
  path = require('path'),
  multiparty = require('connect-multiparty'),
  multipartyMiddleware = multiparty(),
  PORT = process.env.PORT || 27372;

var app = express();

app.use(express.static(path.join(__dirname, 'public')));

app.post('/api/upload', multipartyMiddleware, function(req, res) {
  var file = req.files.file;
  console.log(file.name);
  console.log(file.type);
  console.log(file.path);
});

var server = app.listen(PORT, function() {
  var host = server.address().address;
  var port = server.address().port;
  console.log('the App listening at http://%s:%s', host, port);
}); 

Now public folder is served to root url. Here is the client file public/index.html:

<!DOCTYPE html>
<html>
<head lang="en">
  <meta charset="UTF-8">
  <title>Upload example</title>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
  <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
</head>
<body>
  <div class="container">
    <div>
      <h1>Upload example</h1>
      <hr />
      <div ng-app="fileUpload" ng-controller="MyCtrl">
        <button type="button" class="btn btn-default" ngf-select ng-model="file">Upload using model $watch</button>
      </div>
    </div>
  </div>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.min.js"></script>
  <script src="http://rawgit.com/danialfarid/ng-file-upload/master/dist/ng-file-upload.min.js"></script>
  <script>
    var app = angular.module('fileUpload', ['ngFileUpload']);
    app.controller('MyCtrl', ['$scope', 'Upload', function($scope, Upload) {
      $scope.$watch('file', function() {
        var file = $scope.file;
        if (!file) {
          return;
        }
        Upload.upload({
          url: 'api/upload',
          file: file
        }).progress(function(evt) {
          var progressPercentage = parseInt(100.0 * evt.loaded / evt.total);
          console.log('progress: ' + progressPercentage + '% ' + evt.config.file.name);
        }).success(function(data, status, headers, config) {
          console.log('file ' + config.file.name + 'uploaded. Response: ' + data);
        }).error(function(data, status, headers, config) {
          console.log('error status: ' + status);
        })
      });;
    }]);
  </script>
</body>
</html>

Now you can run node app and try it on localhost:27372 with your browser.

(Here is the gist version: https://gist.github.com/aksakalli/1a56072f066d65248885)


EDIT 2

Here is a basic implementation for option 1- Enabling CORS in NodeJS server. I am using cors package to handle header configuration, now app.js code would be like this:

var express = require('express'),
  multiparty = require('connect-multiparty'),
  cors = require('cors'),
  multipartyMiddleware = multiparty(),
  app = express(),
  PORT = process.env.PORT || 27372;    

app.use(cors());

app.post('/api/upload', multipartyMiddleware, function(req, res) {
  var file = req.files.file;
  console.log(file.name);
  console.log(file.type);
  console.log(file.path);
});

var server = app.listen(PORT, function() {
  var host = server.address().address;
  var port = server.address().port;
  console.log('the App listening at http://%s:%s', host, port);
});
Community
  • 1
  • 1
Can Guney Aksakalli
  • 1,320
  • 1
  • 11
  • 24
3

For the first error:

OPTIONS http://127.0.0.1:27372/upload 400 (Bad Request)                                   angular.js:10514

The ng-file-upload Upload service which you are using removes the Content-Type header, as seen here, before the request. But the parse method from multiparty seems to require it. If you are working from the given example from the wiki, I would advise you to also use express and multiparty as middleware, as it is stated in that example.

Your app.js would look like that:

var express = require('express'),
// Requires multiparty
multiparty = require('connect-multiparty'),
multipartyMiddleware = multiparty();

var app = express();

app.all('*', function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "X-Requested-With");
  next();
});

// Example endpoint
app.post('/upload', multipartyMiddleware, function(req, res) {
  // We are able to access req.files.file thanks to
  // the multiparty middleware
  var file = req.files.file;
  console.log(file.type);
  console.log(file.name);
});

app.listen(27372);

For the second error: It's a CORS problem as mentioned. The proposed app.js should allow CORS because of the following lines:

app.all('*', function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "X-Requested-With");
  next();
});
  • For your first answer: dude I stated in my question that I don't know so much Node, so I don't know what do you mean saying I have to "use express and multiparty as middleware"; for the second answer, I restarted the Node server each time I changed something within the code – Gargaroz Aug 02 '15 at 08:11
  • 1
    The [wiki link](https://github.com/danialfarid/ng-file-upload/wiki/Node-example) which you provided, states in the frist sentence, that this example uses express and connect-multiparty (that is a connect middleware package). It also provides links to both, the [express webpage](http://expressjs.com/) and [connect-multiparty](https://github.com/andrewrk/connect-multiparty). I assumed you might have read those links. Also you don't ever include your UserController.js anywhere in your code. – Paul Georg Podlech Aug 02 '15 at 13:16
  • Actually I thought the code on that wiki would have been more ready-to-use, as I just needed simple server to test my front end. Moreover, @Can Guney seems to not include UserController.js too in the solutions he provided, that's a bit confusing for me – Gargaroz Aug 02 '15 at 13:24
  • I edited my answer. It contains the ready to use example which you expected from the wiki. The UserController.js is omitted, since it is actually very unnecessary for the example. Instead a callback function is provided in the app.js. – Paul Georg Podlech Aug 02 '15 at 14:01
  • @Gargaroz `module.exports` exports given definition to another nodejs file. It is basically giving `app.post` a function parameter would handle the request. Our examples have literal function as parameter. It is also possible for us to pass the handler as reference... – Can Guney Aksakalli Aug 03 '15 at 13:41