60

I know there are a lot of questions like this, but none I've seen have fixed my issue. I've used at least 3 microframeworks already. All of them fail at doing a simple POST, which should return the data back:

The angularJS client:

var app = angular.module('client', []);

app.config(function ($httpProvider) {
  //uncommenting the following line makes GET requests fail as well
  //$httpProvider.defaults.headers.common['Access-Control-Allow-Headers'] = '*';
  delete $httpProvider.defaults.headers.common['X-Requested-With'];
});

app.controller('MainCtrl', function($scope, $http) {
  var baseUrl = 'http://localhost:8080/server.php'

  $scope.response = 'Response goes here';

  $scope.sendRequest = function() {
    $http({
      method: 'GET',
      url: baseUrl + '/get'
    }).then(function successCallback(response) {
      $scope.response = response.data.response;
    }, function errorCallback(response) { });
  };

  $scope.sendPost = function() {
    $http.post(baseUrl + '/post', {post: 'data from client', withCredentials: true })
    .success(function(data, status, headers, config) {
      console.log(status);
    })
    .error(function(data, status, headers, config) {
      console.log('FAILED');
    });
  }
});

The SlimPHP server:

<?php
    require 'vendor/autoload.php';

    $app = new \Slim\Slim();
    $app->response()->headers->set('Access-Control-Allow-Headers', 'Content-Type');
    $app->response()->headers->set('Content-Type', 'application/json');
    $app->response()->headers->set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
    $app->response()->headers->set('Access-Control-Allow-Origin', '*');

    $array = ["response" => "Hello World!"];

    $app->get('/get', function() use($array) {
        $app = \Slim\Slim::getInstance();

        $app->response->setStatus(200);
        echo json_encode($array);
    }); 

    $app->post('/post', function() {
        $app = \Slim\Slim::getInstance();

        $allPostVars = $app->request->post();
        $dataFromClient = $allPostVars['post'];
        $app->response->setStatus(200);
        echo json_encode($dataFromClient);
    });

    $app->run();

I have enabled CORS, and GET requests work. The html updates with the JSON content sent by the server. However I get a

XMLHttpRequest cannot load http://localhost:8080/server.php/post. Response for preflight has invalid HTTP status code 404

Everytime I try to use POST. Why?

EDIT: The req/res as requested by Pointy req/res headers

Deegriz
  • 1,905
  • 1
  • 14
  • 28
  • Well what does the preflight HTTP request/response look like? – Pointy Nov 11 '15 at 22:08
  • Hmm well don't you need an explicit route for the "OPTIONS" request? You've only got routes for "GET" and "POST". – Pointy Nov 11 '15 at 22:15
  • Apparently it forces an OPTIONS request when I try doing POST. Shouldn't it work with POS T? Am I obliged to handle OPTIONS instead? Why? – Deegriz Nov 11 '15 at 22:43
  • 1
    When the POST request has certain characteristics, the browser does that "preflight" OPTIONS transaction first. The POST has to be "simple" in order to avoid it - that means it's got to use a `Content-Type` of `application/x-www-form-urlencoded`, `multipart/form-data`, or `text/plain`, and it can't have any custom headers. I'm not sure exactly what it is about your POST that's triggering the preflight test. – Pointy Nov 11 '15 at 23:04
  • Probably because I'm sending a JSON object. Thanks for the input, I'll try to implement it once I'm back in the code – Deegriz Nov 11 '15 at 23:09
  • Well, handling options on server side didn't change anything – Deegriz Nov 11 '15 at 23:58
  • related problem, having the same error, but with a GET: http://stackoverflow.com/questions/41193190/how-to-connect-from-angular-to-express-server-running-on-the-same-machine/41197537#41197537 – pr00thmatic Dec 17 '16 at 10:19
  • possible solution https://stackoverflow.com/questions/47082492/how-to-send-post-request-using-http-from-angular-http?noredirect=1#47082492 – Akshay Vijay Jain Nov 02 '17 at 20:29

3 Answers3

84

EDIT:

It's been years, but I feel obliged to comment on this further. Now I actually am a developer. Requests to your back-end are usually authenticated with a token which your frameworks will pick up and handle; and this is what was missing. I'm actually not sure how this solution worked at all.

ORIGINAL:

Ok so here's how I figured this out. It all has to do with CORS policy. Before the POST request, Chrome was doing a preflight OPTIONS request, which should be handled and acknowledged by the server prior to the actual request. Now this is really not what I wanted for such a simple server. Hence, resetting the headers client side prevents the preflight:

app.config(function ($httpProvider) {
  $httpProvider.defaults.headers.common = {};
  $httpProvider.defaults.headers.post = {};
  $httpProvider.defaults.headers.put = {};
  $httpProvider.defaults.headers.patch = {};
});

The browser will now send a POST directly. Hope this helps a lot of folks out there... My real problem was not understanding CORS enough.

Link to a great explanation: http://www.html5rocks.com/en/tutorials/cors/

Kudos to this answer for showing me the way.

Deegriz
  • 1,905
  • 1
  • 14
  • 28
  • 37
    Since this is getting a lot of views, I should probably mention that this is NOT what you want for a production app.You should handle the preflight accordingly. – Deegriz Dec 14 '15 at 00:04
  • 8
    This changes nothing for me... Don't understand why or what it should do – nclsvh Jan 05 '16 at 20:23
  • 2
    Don't forget to add $httpProvider.defaults.headers.get = {}; if you are performing a $http.get() request. – Vincil Bishop Jan 16 '16 at 20:11
  • 6
    @AlexOlival amazing input, do you care to share with everyone why this is NOT what you should do and also what it is you SHOULD do – twigg Jan 26 '16 at 10:35
  • 1
    @AlexOlival how should this be handled in production more exactly ? As far as I understand, the server should allow the OPTIONS method on all requests, am I right ? – AdelaN Jan 26 '16 at 13:42
  • 1
    I am by no means a web developer. This was done in academic context for a project which required me to develop both the front and back end. From my research there are some ways to handle a preflight request in case your application communicates with another server such as http://www.dinochiesa.net/?p=754. Please note that this solution was meant for situations such as mine (testing a server and a client locally) – Deegriz Jan 26 '16 at 17:52
  • I had your problem: http://stackoverflow.com/questions/35309831/cors-with-asp-net-mvc-returns-302-on-chrome-firefox-but-ie-works?noredirect=1#comment58431192_35309831 – HelloWorld Feb 12 '16 at 13:06
  • Hey man, this is solving me the problem of sending request, making my reuqest being handled by the server, but at the frontend i dont see any data in the response header, do you know why? – mattobob Mar 24 '17 at 15:54
  • It's been years, but I feel obliged to comment on this further. Now I actually am a developer. Requests to your back-end are usually authenticated with a token which your frameworks will pick up and handle; and this is what was missing. – Deegriz Jan 23 '21 at 22:47
  • The solution suggested in the link worked for me: https://stackoverflow.com/a/49069973/1785629 – Jhabar Mar 15 '21 at 16:56
8

You have enabled CORS and enabled Access-Control-Allow-Origin : * in the server.If still you get GET method working and POST method is not working then it might be because of the problem of Content-Type and data problem.

First AngularJS transmits data using Content-Type: application/json which is not serialized natively by some of the web servers (notably PHP). For them we have to transmit the data as Content-Type: x-www-form-urlencoded

Example :-

        $scope.formLoginPost = function () {
            $http({
                url: url,
                method: "POST",
                data: $.param({ 'username': $scope.username, 'Password': $scope.Password }),
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
            }).then(function (response) {
                // success
                console.log('success');
                console.log("then : " + JSON.stringify(response));
            }, function (response) { // optional
                // failed
                console.log('failed');
                console.log(JSON.stringify(response));
            });
        };

Note : I am using $.params to serialize the data to use Content-Type: x-www-form-urlencoded. Alternatively you can use the following javascript function

function params(obj){
    var str = "";
    for (var key in obj) {
        if (str != "") {
            str += "&";
        }
        str += key + "=" + encodeURIComponent(obj[key]);
    }
    return str;
}

and use params({ 'username': $scope.username, 'Password': $scope.Password }) to serialize it as the Content-Type: x-www-form-urlencoded requests only gets the POST data in username=john&Password=12345 form.

jafarbtech
  • 5,941
  • 1
  • 29
  • 49
3

For a Node.js app, in the server.js file before registering all of my own routes, I put the code below. It sets the headers for all responses. It also ends the response gracefully if it is a pre-flight "OPTIONS" call and immediately sends the pre-flight response back to the client without "nexting" (is that a word?) down through the actual business logic routes. Here is my server.js file. Relevant sections highlighted for Stackoverflow use.

// server.js

// ==================
// BASE SETUP

// import the packages we need
var express    = require('express');
var app        = express();
var bodyParser = require('body-parser');
var morgan     = require('morgan');
var jwt        = require('jsonwebtoken'); // used to create, sign, and verify tokens

// ====================================================
// configure app to use bodyParser()
// this will let us get the data from a POST
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

// Logger
app.use(morgan('dev'));

// -------------------------------------------------------------
// STACKOVERFLOW -- PAY ATTENTION TO THIS NEXT SECTION !!!!!
// -------------------------------------------------------------

//Set CORS header and intercept "OPTIONS" preflight call from AngularJS
var allowCrossDomain = function(req, res, next) {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
    res.header('Access-Control-Allow-Headers', 'Content-Type');
    if (req.method === "OPTIONS") 
        res.send(200);
    else 
        next();
}

// -------------------------------------------------------------
// STACKOVERFLOW -- END OF THIS SECTION, ONE MORE SECTION BELOW
// -------------------------------------------------------------


// =================================================
// ROUTES FOR OUR API

var route1 = require("./routes/route1");
var route2 = require("./routes/route2");
var error404 = require("./routes/error404");


// ======================================================
// REGISTER OUR ROUTES with app

// -------------------------------------------------------------
// STACKOVERFLOW -- PAY ATTENTION TO THIS NEXT SECTION !!!!!
// -------------------------------------------------------------

app.use(allowCrossDomain);

// -------------------------------------------------------------
//  STACKOVERFLOW -- OK THAT IS THE LAST THING.
// -------------------------------------------------------------

app.use("/api/v1/route1/", route1);
app.use("/api/v1/route2/", route2);
app.use('/', error404);

// =================
// START THE SERVER

var port = process.env.PORT || 8080;        // set our port
app.listen(port);
console.log('API Active on port ' + port);
hoekma
  • 645
  • 8
  • 17