Update 6 July: Thanks to @gettleto I've managed to find the response
header options and OPTIONS route that works without errors, but I kind of hacked my way around and frankly don't understand which response
headers should be listed, what might be missing, if any are extraneous or if the API is vulnerable.
If there is a 'best practices' or a guide anyone can refer me to, that would be terrific. I've spent a few hours trying to find a guide for the uninitiated, but no love.
Update 27 June: I discovered that Backbone sends a preflight request to the server before the POST, which led me to this SO answer. I added the OPTIONS route to the API (see code below), and get XMLHttpRequest cannot load http://foxworkx.dev/bookAPI.php/books. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:8081' is therefore not allowed access.
in the console, BUT the API allowed the POST to work!?
Both the OPTIONS and POST requests are getting a status:200 OK.
So, It seems odd that I set CorsOptions and add to $app, but need an OPTIONS route with defined response headers.
Any help appreciated. /Update 27 June
I am working thru a Backbone tutorial, and built a simple MySQL/PHP API using Slim3 and CorsSlim3. The GET is working with no errors. The weird part is the POST is INSERTing into MySQL, so I'm thinking it's a callback issue, but I have no idea how to fix it.
Any help greatly appreciated.
My PHP API code
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
require 'vendor/autoload.php';
$app = new \Slim\App;
// CORS
$corsOptions = array(
"origin" => array( "http://127.0.0.1:8081" ),
"allowMethods" => array('GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'),
"exposeHeaders" => array( "X-Requested-With", "Content-Type", "Accept", "Origin", "Authorization" ),
"allowHeaders" => array( "X-Requested-With", "Content-Type", "Accept", "Origin", "Authorization" )
);
$cors = new \CorsSlim\CorsSlim( $corsOptions );
$app->add( $cors );
// END CORS
/*
ROUTES
*/
// GET all
$app->get( '/books', function ( Request $request, Response $response ) {
$sql = "select * FROM bookDB ORDER BY title";
try {
$db = getConnection();
$stmt = $db->query($sql);
$books = $stmt->fetchAll(PDO::FETCH_OBJ);
$db = null;
echo json_encode($books);
} catch( PDOException $e ) {
echo '{"error":{"text":'. $e->getMessage() .'}}';
}
} );
// OPTIONS ( preflighted requests )------NEW
$app->options( '/books',
function( Request $request, Response $response, $args ) {
return $response
->withHeader('Access-Control-Allow-Origin', 'http://127.0.0.1:8081')
->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization')
->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
}
);
// ADD one
$app->post('/books', function ( Request $request, Response $response ) {
$data = $request->getParsedBody();
$book = [];
$book[ 'title' ] = filter_var( $data[ 'title' ] , FILTER_SANITIZE_STRING );
$book[ 'author' ] = filter_var( $data[ 'author' ] , FILTER_SANITIZE_STRING );
$book[ 'releaseDate' ] = filter_var( $data[ 'releaseDate' ] , FILTER_SANITIZE_STRING );
$book[ 'keywords' ] = filter_var( $data[ 'keywords' ] , FILTER_SANITIZE_STRING );
$sql = "INSERT INTO bookDB ( title,author,releaseDate,keywords ) VALUES ( '" . $book[ 'title' ] . "', '" . $book[ 'author' ] . "', '" . $book[ 'releaseDate' ] . "', '" . $book[ 'keywords' ] . "' )";
try {
$db = getConnection();
$stmt = $db->prepare($sql);
$stmt->execute();
// $book[ 'id' ] = $db->lastInsertId();
// error_log("book id:", $book[ 'id' ] );
$db = null;
// echo json_encode($book );
} catch(PDOException $e) {
error_log( $e->getMessage() );
echo '{"error":{"text":'. $e->getMessage() .'}}';
}
// return $response
// ->withJson($book)
// ->withJson( array( error => array( "text" => $e->getMessage() ) ) );
} );
And my Backbone.view
var app = app || {};
app.LibraryView = Backbone.View.extend({
events: {
'click #add' : 'addBook'
},
el: '#books',
initialize: function() {
this.collection = new app.Library();
this.collection.fetch( { reset:true } );
this.render();
this.listenTo( this.collection, 'add', this.renderBook );
this.listenTo( this.collection, 'reset', this.render );
},
// render library by rendering each book in its collection
render: function() {
this.collection.each(function( item ) {
this.renderBook( item );
},
this );
},
// render a book by creating a BookView and appending the
// element it renders to the library's element
renderBook: function( item ) {
var bookView = new app.BookView({
model: item
});
this.$el.append( bookView.render().el );
},
addBook: function( e ) {
e.preventDefault();
var formData = {};
$( '#addBook div' ).children( 'input' ).each( function( i, el ) {
if( $( el ).val() != '' ) {
formData[ el.id ] = $( el ).val();
}
// Clear input field value
$( el ).val('');
} );
// add to this.collection AND POST the data
this.collection.create( formData );
}
});
And my PHP error_log:
[24-Jun-2016 09:59:15 America/Los_Angeles] PHP Deprecated: Automatically populating $HTTP_RAW_POST_DATA is deprecated and will be removed in a future version. To avoid this warning set 'always_populate_raw_post_data' to '-1' in php.ini and use the php://input stream instead. in Unknown on line 0
[24-Jun-2016 09:59:15 America/Los_Angeles] PHP Warning: Cannot modify header information - headers already sent in Unknown on line 0