0
0
mirror of https://github.com/mongodb/mongo.git synced 2024-12-01 01:21:03 +01:00
mongodb/db/dbwebserver.cpp

478 lines
16 KiB
C++
Raw Normal View History

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"
#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"
#include "instance.h"
#include "security.h"
2008-11-30 02:01:58 +01:00
#include <pcrecpp.h>
#include <boost/date_time/posix_time/posix_time.hpp>
2009-01-14 23:09:51 +01:00
namespace mongo {
extern string bind_ip;
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
}
*/
struct Timing {
Timing() {
start = timeLocked = 0;
2008-12-01 03:00:54 +01:00
}
unsigned long long start, timeLocked;
};
Timing tlast;
const int NStats = 32;
string lockStats[NStats];
unsigned q = 0;
extern bool cpu;
void statsThread() {
2009-10-14 21:59:55 +02:00
Client::initThread();
unsigned long long timeLastPass = 0;
2008-12-29 02:28:49 +01:00
while ( 1 ) {
{
Timer lktm;
dblock lk;
2009-02-09 21:57:30 +01:00
Top::completeSnapshot();
q = (q+1)%NStats;
Timing timing;
dbMutexInfo.timingInfo(timing.start, timing.timeLocked);
unsigned long long now = curTimeMicros64();
if ( timeLastPass ) {
unsigned long long dt = now - timeLastPass;
unsigned long long dlocked = timing.timeLocked - tlast.timeLocked;
{
stringstream ss;
ss << dt / 1000 << '\t';
ss << dlocked / 1000 << '\t';
if ( dt )
ss << (dlocked*100)/dt << '%';
string s = ss.str();
if ( cpu )
log() << "cpu: " << s << endl;
lockStats[q] = s;
2009-10-08 18:18:56 +02:00
ClientCursor::idleTimeReport( (unsigned) ((dt - dlocked)/1000) );
}
}
timeLastPass = now;
tlast = timing;
}
sleepsecs(4);
2008-12-05 22:45:10 +01:00
}
2008-12-01 03:00:54 +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
class DbWebServer : public MiniWebServer {
public:
// caller locks
void doLockedStuff(stringstream& ss) {
ss << "currentOp: " << currentOp.infoNoauth() << "\n";
ss << "# databases: " << databases.size() << '\n';
if ( cc().database() ) {
ss << "curclient: " << cc().database()->name;
ss << '\n';
}
2009-10-08 18:18:56 +02:00
ss << bold(ClientCursor::byLocSize()>10000) << "Cursors byLoc.size(): " << ClientCursor::byLocSize() << bold() << '\n';
ss << "\n<b>replication</b>\n";
ss << "master: " << master << '\n';
ss << "slave: " << slave << '\n';
if ( replPair ) {
ss << "replpair:\n";
ss << replPair->getInfo();
}
bool seemCaughtUp = getInitialSyncCompleted();
if ( !seemCaughtUp ) ss << "<b>";
ss << "initialSyncCompleted: " << seemCaughtUp;
if ( !seemCaughtUp ) ss << "</b>";
ss << '\n';
2009-02-09 21:57:30 +01:00
ss << "\n<b>DBTOP</b>\n";
2009-05-19 00:17:27 +02:00
ss << "<table border=1><tr align='left'><th>Namespace</th><th>%</th><th>Reads</th><th>Writes</th><th>Calls</th><th>Time</th>";
2009-02-09 21:57:30 +01:00
vector< Top::Usage > usage;
Top::usage( usage );
for( vector< Top::Usage >::iterator i = usage.begin(); i != usage.end(); ++i )
2009-05-19 00:17:27 +02:00
ss << setprecision( 2 ) << fixed << "<tr><td>" << i->ns << "</td><td>" << i->pct << "</td><td>"
<< i->reads << "</td><td>" << i->writes << "</td><td>" << i->calls << "</td><td>" << i->time << "</td></tr>\n";
2009-02-09 21:57:30 +01:00
ss << "</table>";
ss << "\n<b>dt\ttlocked</b>\n";
unsigned i = q;
while ( 1 ) {
ss << lockStats[i] << '\n';
i = (i-1)%NStats;
if ( i == q )
break;
}
2008-12-31 15:17:30 +01:00
}
2009-01-14 23:17:24 +01:00
void doUnlockedStuff(stringstream& ss) {
/* this is in the header already ss << "port: " << port << '\n'; */
ss << mongodVersion() << "\n";
ss << "git hash: " << gitVersion() << "\n";
ss << "sys info: " << sysInfo() << "\n";
ss << "\n";
ss << "dblocked: " << dbMutexInfo.isLocked() << " (initial)\n";
ss << "uptime: " << time(0)-started << " seconds\n";
if ( replAllDead )
ss << "<b>replication replAllDead=" << replAllDead << "</b>\n";
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
ss << "\nreplInfo: " << replInfo << '\n';
ss << "currentOp (unlocked): " << currentOp.infoNoauth() << "\n";
}
bool allowed( const char * rq , vector<string>& headers, const SockAddr &from ){
if ( from.localhost() )
return true;
2009-03-19 21:23:04 +01:00
if ( db.findOne( "admin.system.users" , BSONObj() ).isEmpty() )
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
virtual void doRequest(
const char *rq, // the full request
string url,
// set these and return them:
string& responseMsg,
int& responseCode,
vector<string>& headers, // if completely empty, content-type: text/html will be added
const SockAddr &from
)
2008-12-04 20:33:18 +01:00
{
//out() << "url [" << url << "]" << endl;
if ( url.size() > 1 ) {
if ( ! allowed( rq , headers, from ) ){
responseCode = 401;
responseMsg = "not allowed\n";
return;
}
handleRESTRequest( rq , url , responseMsg , responseCode , headers );
return;
}
2008-11-30 02:01:58 +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 << ' ';
dbname = z.str();
2008-11-30 02:01:58 +01:00
}
ss << dbname << "</title></head><body><h2>" << dbname << "</h2><p>\n<pre>";
doUnlockedStuff(ss);
int n = 2000;
Timer t;
while ( 1 ) {
if ( !dbMutexInfo.isLocked() ) {
{
dblock lk;
ss << "time to get dblock: " << t.millis() << "ms\n";
doLockedStuff(ss);
}
break;
}
sleepmillis(1);
if ( --n < 0 ) {
ss << "\n<b>timed out getting dblock</b>\n";
break;
}
2008-11-30 02:01:58 +01:00
}
ss << "</pre></body></html>";
responseMsg = ss.str();
// we want to return SavedContext from before the authentication was performed
if ( ! allowed( rq , headers, from ) ){
responseCode = 401;
responseMsg = "not allowed\n";
return;
}
2008-11-30 02:01:58 +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
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
map<string,string> params;
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
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
for ( string::size_type i=0; i<coll.size(); i++ )
if ( coll[i] == '/' )
coll[i] = '.';
2009-01-14 23:17:24 +01:00
string fullns = dbname + "." + coll;
2009-01-14 23:17:24 +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
stringstream ss;
2009-01-14 23:17:24 +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 << "]";
out() << "don't know how to handle a [" << method << "]" << endl;
}
2009-01-14 23:17:24 +01:00
responseMsg = ss.str();
2008-12-31 21:07:01 +01:00
}
2009-01-14 23:17:24 +01:00
void handleRESTQuery( string ns , string action , map<string,string> & params , int & responseCode , stringstream & out ) {
Timer t;
2008-12-31 21:07:01 +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
int one = 0;
if ( params["one"].size() > 0 && tolower( params["one"][0] ) == 't' ) {
num = 1;
one = 1;
}
2009-01-14 23:17:24 +01:00
BSONObjBuilder queryBuilder;
2009-01-14 23:17:24 +01:00
for ( map<string,string>::iterator i = params.begin(); i != params.end(); i++ ) {
if ( ! i->first.find( "filter_" ) == 0 )
continue;
2009-01-14 23:17:24 +01:00
const char * field = i->first.substr( 7 ).c_str();
const char * val = i->second.c_str();
char * temp;
2009-01-14 23:17:24 +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-02-09 19:04:32 +01:00
BSONObj query = queryBuilder.obj();
auto_ptr<DBClientCursor> cursor = db.query( ns.c_str() , query, num , skip );
2009-01-14 23:17:24 +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
}
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-06-16 18:14:34 +02:00
out << "\n ],\n\n";
2009-01-14 23:17:24 +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";
out << "}\n";
2008-12-31 17:59:41 +01:00
}
2009-01-14 23:17:24 +01:00
// TODO Generate id and revision per couch POST spec
void handlePost( string ns, const char *body, map<string,string> & params, int & responseCode, stringstream & out ) {
try {
BSONObj obj = fromjson( body );
db.insert( ns.c_str(), obj );
} catch ( ... ) {
responseCode = 400; // Bad Request. Seems reasonable for now.
out << "{ \"ok\" : false }";
return;
}
responseCode = 201;
out << "{ \"ok\" : true }";
}
2009-01-14 23:17:24 +01:00
int _getOption( string val , int def ) {
if ( val.size() == 0 )
return def;
return atoi( val.c_str() );
}
2009-01-14 23:17:24 +01:00
private:
static DBDirectClient db;
};
2008-11-30 02:01:58 +01:00
DBDirectClient DbWebServer::db;
void webServerThread() {
boost::thread thr(statsThread);
Client::initThread();
DbWebServer mini;
2009-08-25 16:24:44 +02:00
int p = cmdLine.port + 1000;
if ( mini.init(bind_ip, p) ) {
2009-04-01 18:26:31 +02:00
registerListenerSocket( mini.socket() );
log() << "web admin interface listening on port " << p << '\n';
mini.run();
}
else {
log() << "warning: web admin interface failed to initialize on port " << p << endl;
}
}
2009-01-14 23:09:51 +01:00
} // namespace mongo