400 error - bad request when attempting to POST _replicate via Cordova

I am struggling with the _replicate command using the REST APIs for Couchbase Lite. Specifically, I am getting a status of 400 with “bad request”.

Here is what my POST call looks like:

POST /_replicate HTTP/1.1
Host: lite.couchbase.
{
   "create_target" : true,
   "source" : "http://my-server.com:4755/scoremore-db/",
   "target" : "scoremore-db",
}

In looking through the forums I have already tried these things to solve the problem:

  1. I am using http in my source call
  2. I am using the correct URL returned by getURL (the host in this case)
  3. I am using the latest version of the plugin (cordova plugin add https://github.com/couchbaselabs/Couchbase-Lite-PhoneGap-Plugin.git)

I have successfully connected via the URL to create the database and even in future attempts can continue to confirm it exists. I cannot figure out why _replicate is failing and there is no additional documentation I can find.

Thanks for the help!

Are you trying to create a replication to your local database from a remote endpoint? If so, then the _replicate endpoint is not the way to do it. The remote server has no way to know anything about how to reach your local database. There are a complex series of steps you need to do to set up a replication between these two, but it is easier to let the Couchbase Lite API handle it for you. I am not familiar with the PhoneGap plugin but surely it has a “Replication” object that you can use right?

That’s invalid JSON — you’ve got a trailing comma on the last line.

I really recommend using a JSON encoder to send requests instead of hand-writing it. If you’re doing this from the command line, the httpie tool is much more convenient than curl because it lets you easily send JSON bodies.

@borrrden: Actually the request is correct (except for the syntax error.) That’s simply how you specify a pull replication in the REST API.

Thanks - my mistake on this specific example. I have tried so many variations of the JSON block. Removing the comma doesn’t matter. I still get the same issue.

Here is the “master” JSON block that I keep iterating trying to find something that works.
var pull = {
create_target : true,
target : “scoremore-db”,
source : “https://my-server.com:4755/scoremore-db/”,
continuous : true
};

I have added and removed create_target and continuous. I have tried variations where I don’t include the port number or use http instead of https. I have tried removing the trailing / on the source URL. None of them work. I have also run them through a JSON parser to make sure it is valid (http://jsonformatter.curiousconcept.com/). Is there any other way to get logging or something that might indicate why it is returning bad request?

What platform is this on, and what version of Couchbase Lite?

On any platform, there will be an HTTP status message returned along with the 400 code. That may have useful info in it. I’m not a JavaScript expert so I don’t know how exactly you get it out of an XmlHttpRequest.

This is running on iOS 8 in the simulator although I can reproduce the same problem on my iPhone 6.

The status message sadly just says “bad request” with no additional information. That is the whole JSON block returned to me.

How do I determine the version of couchbase lite? Is that not just ensuring I have the latest version of the plugin?

Could you show the exact code you’re using to send the request?

Also, I believe you can determine the CBL version by looking at the response to GET /.

Thanks Jens.

Here is the response from GET /

"vendor" : 
    "name" : "Couchbase Lite (Objective C)",
    "version" : "1.0 (unofficial)",
"couchdb" : "Welcome",
"CouchbaseLite" : "Welcome",
"version" : "1.0 (unofficial)"

Here is the code that I am using (in snippets to minimize reading on your part):

Get the URL

// Now safe to use the PhoneGap API
log("onDeviceReady() called, getting Couchbase Lite URL")
if (window.cblite) {
    window.cblite.getURL(function(err, url) {
        if (err) {
            log("error launching Couchbase Lite: " + err)
        } else {
            log("Couchbase Lite running at: " + url);
            var database = new Database( url );
            database.initialize();
        }
    });
} else {
    log("error, Couchbase Lite plugin not found.")
}

Initialize the database. Note that all of these calls work perfectly.

    this.initialize = function() {
        send( "GET", this.baseURL, null, function( status, response ) {
            log( "GET / status - " + status );
            log( "GET / response - " + response );
        });


        // First, we need to check to see if the database is set up with our specific scoremoredb.
        send( "GET", this.baseURL + DATABASE_NAME, null, function( status, response ) {
            if( status === 200 ) {
                // Database is initialized already.  Good to go!
                log( "Database already created.  Good to go" );
                triggerSync();
            } else if( status === 404 ) {
                log( "Database doesn't exist yet.  Create it" );
                // Database was not found.  This is a brand new device so we need to replicate it.
                // First, let's create our database locally.
                send( "PUT", database.getBaseURL() + DATABASE_NAME, null, function( status, response ) {
                    if( status === 201 ) {
                        // Database has been created!
                        log( "Database created!" );
                        triggerSync();
                    } else {
                        // Error.  But why?
                        log( "Database creation error: " + status + "||" + response );
                        // TODO
                    }
                });
            }
        });
    };

}

Trigger Synchronization (replication)

function triggerSync()
{
    log( "In triggerSync" );
    var pull = {
        create_target : true,
        target : "scoremoredb",
        source : "https://my-server.com:4755/scoremoredb/",
        continuous : true
    };

    log( "pull - " + JSON.stringify( pull ));
    log( database.getBaseURL() + "_replicate" );

    send( "POST", database.getBaseURL() + "_replicate", pull, function(status, response) {
        log( "trigger status - " + status );
        log( "trigger response - " + response );
        // TODO!
    });
};

Finally, the send method itself

function send( method, url, json, callback ) {
    if( method === null || method == undefined ) {
        throw "Invalid argument - method must be defined";
    }
    if( url === null || url == undefined ) {
        throw "Invalid argument - url must be defined";
    }
    if( callback === null || typeof callback !== "function" ) {
        throw "Invalid argument - callback must be a valid function";
    }

    var xhr = new XMLHttpRequest();
    log( method + " URL: " + url );
    xhr.open( method, url, true );
    if( json !== null ) {
        xhr.setRequestHeader("Content-Type", "application/json");
    }
    xhr.onreadystatechange = function() {
        if( xhr.readyState === 4 ) {
            // stopSpinner();

            log( "Status: " + xhr.status );
            log( "Response: " + xhr.response );

            callback( xhr.status, xhr.response );
        }
    };

    try {
        if( json !== null ) {
            xhr.send( json );
        } else {
            xhr.send();
        }
    } catch(exception) {
        // Fail silently
        callback( undefined, undefined );
    }
}

Everything works as expected except for the POST to _replicate which is giving me a 400 bad request.

Again, I’m no JS expert, but it looks as though you cannot send a JSON body in an XmlHttpRequest just by passing an Object to send(). Here are the Mozilla docs. It shows several supported parameter types but doesn’t say anything about accepting generic objects or converting to JSON. It’s not totally clear to me from reading those docs how you’d send a plain string, but I think if you convert the object to JSON yourself (isn’t there a toJSON() method, or something like that?) and pass the string to send it’ll work.

Jens,

I owe you a beer for sure! That was a rookie mistake on my part.

For anyone reading this later, the missing part was JSON.stringify( pull ) before calling into send to convert the JSON object into a string for sending with XMLHttpRequest. Good to go now!