2009-10-14 22:29:32 +02:00
|
|
|
/* dbwebserver.cpp
|
|
|
|
|
|
|
|
This is the administrative web page displayed on port 28017.
|
|
|
|
*/
|
2008-11-30 02:01:58 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Copyright (C) 2008 10gen Inc.
|
2008-12-29 02:28:49 +01:00
|
|
|
*
|
2008-11-30 02:01:58 +01:00
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License, version 3,
|
|
|
|
* as published by the Free Software Foundation.
|
2008-12-29 02:28:49 +01:00
|
|
|
*
|
2008-11-30 02:01:58 +01:00
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Affero General Public License for more details.
|
2008-12-29 02:28:49 +01:00
|
|
|
*
|
2008-11-30 02:01:58 +01:00
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "stdafx.h"
|
|
|
|
#include "../util/miniwebserver.h"
|
2009-01-30 21:06:33 +01:00
|
|
|
#include "../util/md5.hpp"
|
2008-11-30 02:01:58 +01:00
|
|
|
#include "db.h"
|
2008-12-01 03:00:54 +01:00
|
|
|
#include "repl.h"
|
|
|
|
#include "replset.h"
|
2009-01-02 22:41:13 +01:00
|
|
|
#include "instance.h"
|
2009-01-20 17:05:53 +01:00
|
|
|
#include "security.h"
|
2010-02-02 23:16:25 +01:00
|
|
|
#include "stats/snapshots.h"
|
2010-02-10 21:34:41 +01:00
|
|
|
#include "background.h"
|
2010-03-18 22:11:02 +01:00
|
|
|
#include "commands.h"
|
2008-11-30 02:01:58 +01:00
|
|
|
|
2009-01-30 21:06:33 +01:00
|
|
|
#include <pcrecpp.h>
|
2009-07-24 16:53:46 +02:00
|
|
|
#include <boost/date_time/posix_time/posix_time.hpp>
|
2009-10-15 22:47:35 +02:00
|
|
|
#undef assert
|
|
|
|
#define assert xassert
|
2009-07-24 16:53:46 +02:00
|
|
|
|
2009-01-14 23:09:51 +01:00
|
|
|
namespace mongo {
|
|
|
|
|
2009-04-29 20:14:51 +02:00
|
|
|
extern string bind_ip;
|
2009-01-15 16:17:11 +01:00
|
|
|
extern const char *replInfo;
|
|
|
|
|
|
|
|
bool getInitialSyncCompleted();
|
|
|
|
|
|
|
|
time_t started = time(0);
|
|
|
|
|
|
|
|
/*
|
|
|
|
string toString() {
|
|
|
|
stringstream ss;
|
|
|
|
unsigned long long dt = last - start;
|
|
|
|
ss << dt/1000;
|
|
|
|
ss << '\t';
|
|
|
|
ss << timeLocked/1000 << '\t';
|
|
|
|
if( dt )
|
|
|
|
ss << (timeLocked*100)/dt << '%';
|
|
|
|
return ss.str();
|
2008-12-01 03:00:54 +01:00
|
|
|
}
|
2009-01-15 16:17:11 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
struct Timing {
|
|
|
|
Timing() {
|
|
|
|
start = timeLocked = 0;
|
2008-12-01 03:00:54 +01:00
|
|
|
}
|
2009-01-15 16:17:11 +01:00
|
|
|
unsigned long long start, timeLocked;
|
|
|
|
};
|
2008-12-01 03:00:54 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
bool _bold;
|
|
|
|
string bold(bool x) {
|
|
|
|
_bold = x;
|
|
|
|
return x ? "<b>" : "";
|
|
|
|
}
|
|
|
|
string bold() {
|
|
|
|
return _bold ? "</b>" : "";
|
2008-12-01 03:00:54 +01:00
|
|
|
}
|
2008-11-30 02:01:58 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
class DbWebServer : public MiniWebServer {
|
|
|
|
public:
|
|
|
|
// caller locks
|
|
|
|
void doLockedStuff(stringstream& ss) {
|
2010-01-02 07:25:53 +01:00
|
|
|
ss << "# databases: " << dbHolder.size() << '\n';
|
2010-02-02 23:16:25 +01:00
|
|
|
|
2009-10-08 18:18:56 +02:00
|
|
|
ss << bold(ClientCursor::byLocSize()>10000) << "Cursors byLoc.size(): " << ClientCursor::byLocSize() << bold() << '\n';
|
2009-01-15 16:17:11 +01:00
|
|
|
ss << "\n<b>replication</b>\n";
|
2010-02-09 03:04:09 +01:00
|
|
|
ss << "master: " << replSettings.master << '\n';
|
|
|
|
ss << "slave: " << replSettings.slave << '\n';
|
2009-01-15 16:17:11 +01:00
|
|
|
if ( replPair ) {
|
|
|
|
ss << "replpair:\n";
|
|
|
|
ss << replPair->getInfo();
|
|
|
|
}
|
|
|
|
bool seemCaughtUp = getInitialSyncCompleted();
|
|
|
|
if ( !seemCaughtUp ) ss << "<b>";
|
|
|
|
ss << "initialSyncCompleted: " << seemCaughtUp;
|
|
|
|
if ( !seemCaughtUp ) ss << "</b>";
|
|
|
|
ss << '\n';
|
2010-02-02 23:16:25 +01:00
|
|
|
|
|
|
|
auto_ptr<SnapshotDelta> delta = statsSnapshots.computeDelta();
|
|
|
|
if ( delta.get() ){
|
|
|
|
ss << "\n<b>DBTOP (occurences|percent of elapsed)</b>\n";
|
|
|
|
ss << "<table border=1>";
|
|
|
|
ss << "<tr align='left'>";
|
2010-03-16 21:01:59 +01:00
|
|
|
ss << "<th>NS</th>"
|
|
|
|
"<th colspan=2>total</th>"
|
|
|
|
"<th colspan=2>Reads</th>"
|
|
|
|
"<th colspan=2>Writes</th>"
|
|
|
|
"<th colspan=2>Queries</th>"
|
|
|
|
"<th colspan=2>GetMores</th>"
|
|
|
|
"<th colspan=2>Inserts</th>"
|
|
|
|
"<th colspan=2>Updates</th>"
|
|
|
|
"<th colspan=2>Removes</th>";
|
2010-02-02 23:16:25 +01:00
|
|
|
ss << "</tr>";
|
|
|
|
|
2010-02-04 20:25:49 +01:00
|
|
|
display( ss , (double) delta->elapsed() , "GLOBAL" , delta->globalUsageDiff() );
|
2010-02-02 23:16:25 +01:00
|
|
|
|
|
|
|
Top::UsageMap usage = delta->collectionUsageDiff();
|
|
|
|
for ( Top::UsageMap::iterator i=usage.begin(); i != usage.end(); i++ ){
|
2010-02-04 20:25:49 +01:00
|
|
|
display( ss , (double) delta->elapsed() , i->first , i->second );
|
2010-02-02 23:16:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ss << "</table>";
|
2009-01-15 16:17:11 +01:00
|
|
|
}
|
2010-02-02 23:16:25 +01:00
|
|
|
|
|
|
|
statsSnapshots.outputLockInfoHTML( ss );
|
|
|
|
|
2010-02-10 21:34:41 +01:00
|
|
|
BackgroundOperation::dump(ss);
|
2010-02-02 23:16:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void display( stringstream& ss , double elapsed , const Top::UsageData& usage ){
|
|
|
|
ss << "<td>";
|
|
|
|
ss << usage.count;
|
2010-03-16 21:01:59 +01:00
|
|
|
ss << "</td><td>";
|
2010-02-02 23:16:25 +01:00
|
|
|
double per = 100 * ((double)usage.time)/elapsed;
|
|
|
|
ss << setprecision(2) << fixed << per << "%";
|
|
|
|
ss << "</td>";
|
|
|
|
}
|
|
|
|
|
|
|
|
void display( stringstream& ss , double elapsed , const string& ns , const Top::CollectionData& data ){
|
|
|
|
if ( ns != "GLOBAL" && data.total.count == 0 )
|
|
|
|
return;
|
|
|
|
ss << "<tr><th>" << ns << "</th>";
|
|
|
|
|
|
|
|
display( ss , elapsed , data.total );
|
|
|
|
|
|
|
|
display( ss , elapsed , data.readLock );
|
|
|
|
display( ss , elapsed , data.writeLock );
|
|
|
|
|
|
|
|
display( ss , elapsed , data.queries );
|
|
|
|
display( ss , elapsed , data.getmore );
|
|
|
|
display( ss , elapsed , data.insert );
|
|
|
|
display( ss , elapsed , data.update );
|
|
|
|
display( ss , elapsed , data.remove );
|
|
|
|
|
|
|
|
ss << "</tr>";
|
2008-12-31 15:17:30 +01:00
|
|
|
}
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2010-02-09 16:35:31 +01:00
|
|
|
void tablecell( stringstream& ss , bool b ){
|
|
|
|
ss << "<td>" << (b ? "<b>X</b>" : "") << "</td>";
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
template< typename T>
|
|
|
|
void tablecell( stringstream& ss , const T& t ){
|
|
|
|
ss << "<td>" << t << "</td>";
|
|
|
|
}
|
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
void doUnlockedStuff(stringstream& ss) {
|
2009-03-25 22:30:30 +01:00
|
|
|
/* this is in the header already ss << "port: " << port << '\n'; */
|
2009-05-12 20:18:17 +02:00
|
|
|
ss << mongodVersion() << "\n";
|
|
|
|
ss << "git hash: " << gitVersion() << "\n";
|
|
|
|
ss << "sys info: " << sysInfo() << "\n";
|
|
|
|
ss << "\n";
|
2009-12-03 19:12:51 +01:00
|
|
|
ss << "dbwritelocked: " << dbMutex.info().isLocked() << " (initial)\n";
|
2009-01-15 16:17:11 +01:00
|
|
|
ss << "uptime: " << time(0)-started << " seconds\n";
|
2009-02-04 19:22:02 +01:00
|
|
|
if ( replAllDead )
|
|
|
|
ss << "<b>replication replAllDead=" << replAllDead << "</b>\n";
|
2009-01-15 16:17:11 +01:00
|
|
|
ss << "\nassertions:\n";
|
|
|
|
for ( int i = 0; i < 4; i++ ) {
|
|
|
|
if ( lastAssert[i].isSet() ) {
|
|
|
|
ss << "<b>";
|
|
|
|
if ( i == 3 ) ss << "usererr";
|
|
|
|
else ss << i;
|
|
|
|
ss << "</b>" << ' ' << lastAssert[i].toString();
|
|
|
|
}
|
|
|
|
}
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2009-12-07 21:42:26 +01:00
|
|
|
ss << "\nreplInfo: " << replInfo << "\n\n";
|
2009-10-16 21:36:34 +02:00
|
|
|
|
2009-12-07 21:42:26 +01:00
|
|
|
ss << "Clients:\n";
|
2010-02-09 16:35:31 +01:00
|
|
|
ss << "<table border=1>";
|
|
|
|
ss << "<tr align='left'>"
|
|
|
|
<< "<th>Thread</th>"
|
|
|
|
|
|
|
|
<< "<th>OpId</th>"
|
|
|
|
<< "<th>Active</th>"
|
|
|
|
<< "<th>LockType</th>"
|
|
|
|
<< "<th>Waiting</th>"
|
|
|
|
<< "<th>SecsRunning</th>"
|
|
|
|
<< "<th>Op</th>"
|
|
|
|
<< "<th>NameSpace</th>"
|
|
|
|
<< "<th>Query</th>"
|
|
|
|
<< "<th>client</th>"
|
2010-03-15 16:18:08 +01:00
|
|
|
<< "<th>msg</th>"
|
|
|
|
<< "<th>progress</th>"
|
2010-02-09 16:35:31 +01:00
|
|
|
|
|
|
|
<< "</tr>\n";
|
2009-10-16 21:36:34 +02:00
|
|
|
{
|
2010-03-15 17:42:01 +01:00
|
|
|
scoped_lock bl(Client::clientsMutex);
|
2009-10-16 21:36:34 +02:00
|
|
|
for( set<Client*>::iterator i = Client::clients.begin(); i != Client::clients.end(); i++ ) {
|
|
|
|
Client *c = *i;
|
|
|
|
CurOp& co = *(c->curop());
|
2010-02-09 16:35:31 +01:00
|
|
|
ss << "<tr><td>" << c->desc() << "</td>";
|
|
|
|
|
|
|
|
tablecell( ss , co.opNum() );
|
|
|
|
tablecell( ss , co.active() );
|
|
|
|
tablecell( ss , co.getLockType() );
|
|
|
|
tablecell( ss , co.isWaitingForLock() );
|
|
|
|
if ( co.active() )
|
|
|
|
tablecell( ss , co.elapsedSeconds() );
|
|
|
|
else
|
|
|
|
tablecell( ss , "" );
|
|
|
|
tablecell( ss , co.getOp() );
|
|
|
|
tablecell( ss , co.getNS() );
|
|
|
|
if ( co.haveQuery() )
|
|
|
|
tablecell( ss , co.query() );
|
|
|
|
else
|
|
|
|
tablecell( ss , "" );
|
|
|
|
tablecell( ss , co.getRemoteString() );
|
|
|
|
|
2010-03-15 16:18:08 +01:00
|
|
|
tablecell( ss , co.getMessage() );
|
|
|
|
tablecell( ss , co.getProgressMeter().toString() );
|
|
|
|
|
|
|
|
|
2010-02-09 16:35:31 +01:00
|
|
|
ss << "</tr>";
|
2009-10-16 21:36:34 +02:00
|
|
|
}
|
|
|
|
}
|
2009-12-07 21:42:26 +01:00
|
|
|
ss << "</table>\n";
|
2009-01-15 16:17:11 +01:00
|
|
|
}
|
2009-01-30 21:06:33 +01:00
|
|
|
|
2009-05-13 18:28:59 +02:00
|
|
|
bool allowed( const char * rq , vector<string>& headers, const SockAddr &from ){
|
|
|
|
|
|
|
|
if ( from.localhost() )
|
|
|
|
return true;
|
2009-01-30 21:06:33 +01:00
|
|
|
|
2010-03-26 21:33:12 +01:00
|
|
|
Client::GodScope gs;
|
|
|
|
|
2010-02-23 23:46:50 +01:00
|
|
|
if ( db.findOne( "admin.system.users" , BSONObj() , 0 , QueryOption_SlaveOk ).isEmpty() )
|
2009-01-30 21:06:33 +01:00
|
|
|
return true;
|
|
|
|
|
|
|
|
string auth = getHeader( rq , "Authorization" );
|
|
|
|
|
|
|
|
if ( auth.size() > 0 && auth.find( "Digest " ) == 0 ){
|
|
|
|
auth = auth.substr( 7 ) + ", ";
|
|
|
|
|
|
|
|
map<string,string> parms;
|
|
|
|
pcrecpp::StringPiece input( auth );
|
|
|
|
|
|
|
|
string name, val;
|
|
|
|
pcrecpp::RE re("(\\w+)=\"?(.*?)\"?, ");
|
|
|
|
while ( re.Consume( &input, &name, &val) ){
|
|
|
|
parms[name] = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
BSONObj user = db.findOne( "admin.system.users" , BSON( "user" << parms["username"] ) );
|
|
|
|
if ( ! user.isEmpty() ){
|
|
|
|
string ha1 = user["pwd"].str();
|
|
|
|
string ha2 = md5simpledigest( (string)"GET" + ":" + parms["uri"] );
|
|
|
|
|
|
|
|
string r = ha1 + ":" + parms["nonce"];
|
|
|
|
if ( parms["nc"].size() && parms["cnonce"].size() && parms["qop"].size() ){
|
|
|
|
r += ":";
|
|
|
|
r += parms["nc"];
|
|
|
|
r += ":";
|
|
|
|
r += parms["cnonce"];
|
|
|
|
r += ":";
|
|
|
|
r += parms["qop"];
|
|
|
|
}
|
|
|
|
r += ":";
|
|
|
|
r += ha2;
|
|
|
|
r = md5simpledigest( r );
|
|
|
|
|
|
|
|
if ( r == parms["response"] )
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
stringstream authHeader;
|
|
|
|
authHeader
|
|
|
|
<< "WWW-Authenticate: "
|
|
|
|
<< "Digest realm=\"mongo\", "
|
|
|
|
<< "nonce=\"abc\", "
|
|
|
|
<< "algorithm=MD5, qop=\"auth\" "
|
|
|
|
;
|
|
|
|
|
|
|
|
headers.push_back( authHeader.str() );
|
|
|
|
return 0;
|
|
|
|
}
|
2008-12-04 20:33:18 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
virtual void doRequest(
|
|
|
|
const char *rq, // the full request
|
|
|
|
string url,
|
|
|
|
// set these and return them:
|
|
|
|
string& responseMsg,
|
|
|
|
int& responseCode,
|
2009-05-13 18:28:59 +02:00
|
|
|
vector<string>& headers, // if completely empty, content-type: text/html will be added
|
|
|
|
const SockAddr &from
|
2009-01-15 16:17:11 +01:00
|
|
|
)
|
2008-12-04 20:33:18 +01:00
|
|
|
{
|
2009-01-15 17:26:38 +01:00
|
|
|
//out() << "url [" << url << "]" << endl;
|
2009-01-30 21:06:33 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
if ( url.size() > 1 ) {
|
2010-03-18 22:11:02 +01:00
|
|
|
|
2010-03-19 21:31:02 +01:00
|
|
|
if ( url.find( "/_status" ) == 0 ){
|
2010-03-18 22:11:02 +01:00
|
|
|
if ( ! allowed( rq , headers, from ) ){
|
|
|
|
responseCode = 401;
|
|
|
|
responseMsg = "not allowed\n";
|
|
|
|
return;
|
|
|
|
}
|
2010-03-19 21:31:02 +01:00
|
|
|
generateServerStatus( url , responseMsg );
|
2010-03-23 17:13:23 +01:00
|
|
|
responseCode = 200;
|
2010-03-18 22:11:02 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-03-04 04:37:41 +01:00
|
|
|
if ( ! cmdLine.rest ){
|
|
|
|
responseCode = 403;
|
|
|
|
responseMsg = "rest is not enabled. use --rest to turn on";
|
|
|
|
return;
|
|
|
|
}
|
2009-05-13 18:28:59 +02:00
|
|
|
if ( ! allowed( rq , headers, from ) ){
|
2009-05-13 17:58:24 +02:00
|
|
|
responseCode = 401;
|
|
|
|
responseMsg = "not allowed\n";
|
|
|
|
return;
|
|
|
|
}
|
2009-01-15 16:17:11 +01:00
|
|
|
handleRESTRequest( rq , url , responseMsg , responseCode , headers );
|
|
|
|
return;
|
|
|
|
}
|
2008-11-30 02:01:58 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
|
|
|
|
responseCode = 200;
|
|
|
|
stringstream ss;
|
|
|
|
ss << "<html><head><title>";
|
|
|
|
|
|
|
|
string dbname;
|
|
|
|
{
|
|
|
|
stringstream z;
|
2009-08-25 16:24:44 +02:00
|
|
|
z << "mongodb " << getHostName() << ':' << mongo::cmdLine.port << ' ';
|
2009-01-15 16:17:11 +01:00
|
|
|
dbname = z.str();
|
2008-11-30 02:01:58 +01:00
|
|
|
}
|
2009-01-15 16:17:11 +01:00
|
|
|
ss << dbname << "</title></head><body><h2>" << dbname << "</h2><p>\n<pre>";
|
|
|
|
|
|
|
|
doUnlockedStuff(ss);
|
|
|
|
|
2010-02-09 18:35:26 +01:00
|
|
|
{
|
|
|
|
Timer t;
|
|
|
|
readlocktry lk( "" , 2000 );
|
|
|
|
if ( lk.got() ){
|
|
|
|
ss << "time to get dblock: " << t.millis() << "ms\n";
|
|
|
|
doLockedStuff(ss);
|
2009-01-15 16:17:11 +01:00
|
|
|
}
|
2010-02-09 18:35:26 +01:00
|
|
|
else {
|
2009-01-15 16:17:11 +01:00
|
|
|
ss << "\n<b>timed out getting dblock</b>\n";
|
|
|
|
}
|
2008-11-30 02:01:58 +01:00
|
|
|
}
|
2010-02-09 18:35:26 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
|
|
|
|
ss << "</pre></body></html>";
|
|
|
|
responseMsg = ss.str();
|
2009-05-13 17:58:24 +02:00
|
|
|
|
2009-10-09 20:59:44 +02:00
|
|
|
// we want to return SavedContext from before the authentication was performed
|
2009-05-13 18:28:59 +02:00
|
|
|
if ( ! allowed( rq , headers, from ) ){
|
2009-05-13 17:58:24 +02:00
|
|
|
responseCode = 401;
|
|
|
|
responseMsg = "not allowed\n";
|
|
|
|
return;
|
|
|
|
}
|
2008-11-30 02:01:58 +01:00
|
|
|
}
|
|
|
|
|
2010-03-19 21:31:02 +01:00
|
|
|
void generateServerStatus( string url , string& responseMsg ){
|
2010-03-18 22:11:02 +01:00
|
|
|
static vector<string> commands;
|
|
|
|
if ( commands.size() == 0 ){
|
|
|
|
commands.push_back( "serverStatus" );
|
|
|
|
commands.push_back( "buildinfo" );
|
|
|
|
}
|
2010-03-19 21:31:02 +01:00
|
|
|
|
|
|
|
BSONObj params;
|
|
|
|
if ( url.find( "?" ) != string::npos ) {
|
|
|
|
parseParams( params , url.substr( url.find( "?" ) + 1 ) );
|
|
|
|
}
|
2010-03-18 22:11:02 +01:00
|
|
|
|
|
|
|
BSONObjBuilder buf(1024);
|
|
|
|
|
|
|
|
for ( unsigned i=0; i<commands.size(); i++ ){
|
|
|
|
string cmd = commands[i];
|
|
|
|
|
|
|
|
Command * c = Command::findCommand( cmd );
|
|
|
|
assert( c );
|
|
|
|
assert( c->locktype() == 0 );
|
2010-03-19 21:31:02 +01:00
|
|
|
|
|
|
|
BSONObj co;
|
|
|
|
{
|
|
|
|
BSONObjBuilder b;
|
|
|
|
b.append( cmd.c_str() , 1 );
|
|
|
|
|
|
|
|
if ( cmd == "serverStatus" && params["repl"].type() ){
|
|
|
|
b.append( "repl" , atoi( params["repl"].valuestr() ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
co = b.obj();
|
|
|
|
}
|
2010-03-18 22:11:02 +01:00
|
|
|
|
|
|
|
string errmsg;
|
|
|
|
|
|
|
|
BSONObjBuilder sub;
|
|
|
|
if ( ! c->run( "admin.$cmd" , co , errmsg , sub , false ) )
|
|
|
|
buf.append( cmd.c_str() , errmsg );
|
|
|
|
else
|
|
|
|
buf.append( cmd.c_str() , sub.obj() );
|
|
|
|
}
|
|
|
|
|
|
|
|
responseMsg = buf.obj().jsonString();
|
|
|
|
}
|
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
void handleRESTRequest( const char *rq, // the full request
|
|
|
|
string url,
|
|
|
|
string& responseMsg,
|
|
|
|
int& responseCode,
|
|
|
|
vector<string>& headers // if completely empty, content-type: text/html will be added
|
|
|
|
) {
|
|
|
|
|
|
|
|
string::size_type first = url.find( "/" , 1 );
|
|
|
|
if ( first == string::npos ) {
|
|
|
|
responseCode = 400;
|
|
|
|
return;
|
|
|
|
}
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
string method = parseMethod( rq );
|
|
|
|
string dbname = url.substr( 1 , first - 1 );
|
|
|
|
string coll = url.substr( first + 1 );
|
|
|
|
string action = "";
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2010-03-12 21:00:01 +01:00
|
|
|
BSONObj params;
|
2009-01-15 16:17:11 +01:00
|
|
|
if ( coll.find( "?" ) != string::npos ) {
|
|
|
|
parseParams( params , coll.substr( coll.find( "?" ) + 1 ) );
|
|
|
|
coll = coll.substr( 0 , coll.find( "?" ) );
|
|
|
|
}
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
string::size_type last = coll.find_last_of( "/" );
|
|
|
|
if ( last == string::npos ) {
|
|
|
|
action = coll;
|
|
|
|
coll = "_defaultCollection";
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
action = coll.substr( last + 1 );
|
|
|
|
coll = coll.substr( 0 , last );
|
|
|
|
}
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
for ( string::size_type i=0; i<coll.size(); i++ )
|
|
|
|
if ( coll[i] == '/' )
|
|
|
|
coll[i] = '.';
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2010-03-04 04:06:03 +01:00
|
|
|
string fullns = urlDecode(dbname + "." + coll);
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
headers.push_back( (string)"x-action: " + action );
|
|
|
|
headers.push_back( (string)"x-ns: " + fullns );
|
|
|
|
headers.push_back( "Content-Type: text/plain;charset=utf-8" );
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
stringstream ss;
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
if ( method == "GET" ) {
|
|
|
|
responseCode = 200;
|
|
|
|
handleRESTQuery( fullns , action , params , responseCode , ss );
|
|
|
|
}
|
|
|
|
else if ( method == "POST" ) {
|
|
|
|
responseCode = 201;
|
|
|
|
handlePost( fullns , body( rq ) , params , responseCode , ss );
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
responseCode = 400;
|
|
|
|
headers.push_back( "X_err: bad request" );
|
|
|
|
ss << "don't know how to handle a [" << method << "]";
|
2009-01-15 17:26:38 +01:00
|
|
|
out() << "don't know how to handle a [" << method << "]" << endl;
|
2009-01-15 16:17:11 +01:00
|
|
|
}
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
responseMsg = ss.str();
|
2008-12-31 21:07:01 +01:00
|
|
|
}
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2010-03-12 21:00:01 +01:00
|
|
|
void handleRESTQuery( string ns , string action , BSONObj & params , int & responseCode , stringstream & out ) {
|
2009-01-15 16:17:11 +01:00
|
|
|
Timer t;
|
2008-12-31 21:07:01 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
int skip = _getOption( params["skip"] , 0 );
|
|
|
|
int num = _getOption( params["limit"] , _getOption( params["count" ] , 1000 ) ); // count is old, limit is new
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
int one = 0;
|
2010-03-12 21:00:01 +01:00
|
|
|
if ( params["one"].type() == String && tolower( params["one"].valuestr()[0] ) == 't' ) {
|
2009-01-15 16:17:11 +01:00
|
|
|
num = 1;
|
|
|
|
one = 1;
|
|
|
|
}
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
BSONObjBuilder queryBuilder;
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2010-03-12 21:00:01 +01:00
|
|
|
BSONObjIterator i(params);
|
|
|
|
while ( i.more() ){
|
|
|
|
BSONElement e = i.next();
|
|
|
|
string name = e.fieldName();
|
|
|
|
if ( ! name.find( "filter_" ) == 0 )
|
2009-01-15 16:17:11 +01:00
|
|
|
continue;
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2010-03-12 21:00:01 +01:00
|
|
|
const char * field = name.substr( 7 ).c_str();
|
|
|
|
const char * val = e.valuestr();
|
2009-01-05 22:12:44 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
char * temp;
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
// TODO: this is how i guess if something is a number. pretty lame right now
|
|
|
|
double number = strtod( val , &temp );
|
|
|
|
if ( temp != val )
|
|
|
|
queryBuilder.append( field , number );
|
|
|
|
else
|
|
|
|
queryBuilder.append( field , val );
|
|
|
|
}
|
2009-01-05 22:12:44 +01:00
|
|
|
|
2009-02-09 19:04:32 +01:00
|
|
|
BSONObj query = queryBuilder.obj();
|
2009-01-05 22:12:44 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
auto_ptr<DBClientCursor> cursor = db.query( ns.c_str() , query, num , skip );
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
if ( one ) {
|
|
|
|
if ( cursor->more() ) {
|
|
|
|
BSONObj obj = cursor->next();
|
|
|
|
out << obj.jsonString() << "\n";
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
responseCode = 404;
|
|
|
|
}
|
|
|
|
return;
|
2008-12-31 21:07:01 +01:00
|
|
|
}
|
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
out << "{\n";
|
|
|
|
out << " \"offset\" : " << skip << ",\n";
|
|
|
|
out << " \"rows\": [\n";
|
|
|
|
|
|
|
|
int howMany = 0;
|
|
|
|
while ( cursor->more() ) {
|
|
|
|
if ( howMany++ )
|
|
|
|
out << " ,\n";
|
|
|
|
BSONObj obj = cursor->next();
|
|
|
|
out << " " << obj.jsonString();
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
}
|
2009-06-16 18:14:34 +02:00
|
|
|
out << "\n ],\n\n";
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
out << " \"total_rows\" : " << howMany << " ,\n";
|
|
|
|
out << " \"query\" : " << query.jsonString() << " ,\n";
|
2009-06-16 18:14:34 +02:00
|
|
|
out << " \"millis\" : " << t.millis() << "\n";
|
2009-01-15 16:17:11 +01:00
|
|
|
out << "}\n";
|
2008-12-31 17:59:41 +01:00
|
|
|
}
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
// TODO Generate id and revision per couch POST spec
|
2010-03-12 21:00:01 +01:00
|
|
|
void handlePost( string ns, const char *body, BSONObj& params, int & responseCode, stringstream & out ) {
|
2009-01-15 16:17:11 +01:00
|
|
|
try {
|
|
|
|
BSONObj obj = fromjson( body );
|
|
|
|
db.insert( ns.c_str(), obj );
|
|
|
|
} catch ( ... ) {
|
|
|
|
responseCode = 400; // Bad Request. Seems reasonable for now.
|
|
|
|
out << "{ \"ok\" : false }";
|
|
|
|
return;
|
|
|
|
}
|
2009-01-07 16:08:12 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
responseCode = 201;
|
|
|
|
out << "{ \"ok\" : true }";
|
2009-01-07 16:08:12 +01:00
|
|
|
}
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2010-03-12 21:00:01 +01:00
|
|
|
int _getOption( BSONElement e , int def ) {
|
|
|
|
if ( e.isNumber() )
|
|
|
|
return e.numberInt();
|
|
|
|
if ( e.type() == String )
|
|
|
|
return atoi( e.valuestr() );
|
|
|
|
return def;
|
2009-01-15 16:17:11 +01:00
|
|
|
}
|
2009-01-14 23:17:24 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
private:
|
|
|
|
static DBDirectClient db;
|
|
|
|
};
|
2008-11-30 02:01:58 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
DBDirectClient DbWebServer::db;
|
2009-01-07 16:08:12 +01:00
|
|
|
|
2009-01-15 16:17:11 +01:00
|
|
|
void webServerThread() {
|
2009-10-16 21:36:34 +02:00
|
|
|
Client::initThread("websvr");
|
2009-01-15 16:17:11 +01:00
|
|
|
DbWebServer mini;
|
2009-08-25 16:24:44 +02:00
|
|
|
int p = cmdLine.port + 1000;
|
2009-04-29 20:14:51 +02:00
|
|
|
if ( mini.init(bind_ip, p) ) {
|
2010-01-09 02:58:12 +01:00
|
|
|
ListeningSockets::get()->add( mini.socket() );
|
|
|
|
log() << "web admin interface listening on port " << p << endl;
|
2009-01-15 16:17:11 +01:00
|
|
|
mini.run();
|
2009-03-25 22:30:30 +01:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
log() << "warning: web admin interface failed to initialize on port " << p << endl;
|
|
|
|
}
|
2009-10-16 21:36:34 +02:00
|
|
|
cc().shutdown();
|
2009-01-15 16:17:11 +01:00
|
|
|
}
|
2009-01-14 23:09:51 +01:00
|
|
|
|
|
|
|
} // namespace mongo
|