Skip to content
This repository has been archived by the owner on Feb 14, 2020. It is now read-only.

Commit

Permalink
Rewrite /api/config and zap /api/configlables
Browse files Browse the repository at this point in the history
I want to add some "post config" hooks, while there I take the
opportunity to move the code into another file and refactor such
that /api/configlabels is no longer needed.

I will delay the commit of the post config hooks for a while,
because the patch is not ready, but this piece can for sure go
in for testing.

Tested by making sure that query string options are honored, that
invalid privacy settings and invalid interval settings are detected,
and that, more generally, the web interface seems OK.
  • Loading branch information
bassosimone committed Feb 2, 2012
1 parent 0cd96f2 commit bd084f7
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 60 deletions.
61 changes: 2 additions & 59 deletions neubot/api/server.py
Expand Up @@ -47,6 +47,7 @@
from neubot.speedtest.client import QUEUE_HISTORY
from neubot.state import STATE

from neubot import config_api
from neubot import privacy
from neubot import log_api
from neubot import runner_api
Expand All @@ -60,8 +61,7 @@ def __init__(self, poller):
"/api": self._api,
"/api/": self._api,
"/api/bittorrent": self._api_bittorrent,
"/api/config": self._api_config,
"/api/configlabels": self._api_configlabels,
"/api/config": config_api.config_api,
"/api/debug": self._api_debug,
"/api/index": self._api_index,
"/api/exit": self._api_exit,
Expand Down Expand Up @@ -133,63 +133,6 @@ def _api_bittorrent(self, stream, request, query):
mimetype=mimetype)
stream.send_response(request, response)

def _api_config(self, stream, request, query):
response = Message()

indent, mimetype, sort_keys = None, "application/json", False
dictionary = cgi.parse_qs(query)
if "debug" in dictionary and utils.intify(dictionary["debug"][0]):
indent, mimetype, sort_keys = 4, "text/plain", True

if request.method == "POST":
s = request.body.read()
updates = qs_to_dictionary(s)
privacy.check(updates)

#
# Neubot servers are a shared resource, better not
# allowing for too frequent automatic tests. A lot
# of users with less tests per user is better than
# a lot of tests from few users. I hope that people
# that want to modify this setting understand.
#
if "agent.interval" in updates:
interval = int(updates["agent.interval"])
if interval < 1380 and interval != 0:
raise ConfigError('''
Invalid agent.interval! It must be >= 1380 or 0, which hints Neubot
that it should extract a random value in a given interval. The reason
why we don't allow to run automatic tests too often is that Neubot
servers are a shared resources between many users, so if you run tests
too frequently that is unfair to other users.
''')

CONFIG.merge_api(updates, DATABASE.connection())
STATE.update("config", updates)
# Empty JSON b/c '204 No Content' is treated as an error
s = "{}"
else:
s = json.dumps(CONFIG.conf, sort_keys=sort_keys, indent=indent)

stringio = StringIO.StringIO(s)
response.compose(code="200", reason="Ok", body=stringio,
mimetype=mimetype)
stream.send_response(request, response)

def _api_configlabels(self, stream, request, query):

indent, mimetype = None, "application/json"
dictionary = cgi.parse_qs(query)
if "debug" in dictionary and utils.intify(dictionary["debug"][0]):
indent, mimetype = 4, "text/plain"

response = Message()
s = json.dumps(CONFIG.descriptions, sort_keys=True, indent=indent)
stringio = StringIO.StringIO(s)
response.compose(code="200", reason="Ok", body=stringio,
mimetype=mimetype)
stream.send_response(request, response)

def _api_debug(self, stream, request, query):
response = Message()
debuginfo = {}
Expand Down
132 changes: 132 additions & 0 deletions neubot/config_api.py
@@ -0,0 +1,132 @@
# neubot/config_api.py

#
# Copyright (c) 2011-2012 Simone Basso <bassosimone@gmail.com>,
# NEXA Center for Internet & Society at Politecnico di Torino
#
# This file is part of Neubot <http://www.neubot.org/>.
#
# Neubot is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Neubot 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Neubot. If not, see <http://www.gnu.org/licenses/>.
#

''' Implements /api/config API '''

# Adapted from neubot/api/server.py

import cgi

from neubot.compat import json
from neubot.config import ConfigError
from neubot.config import CONFIG
from neubot.database import DATABASE
from neubot.http.message import Message
from neubot.state import STATE

from neubot import marshal
from neubot import privacy
from neubot import utils

def config_api(stream, request, query):

''' Implements /api/config API '''

# Adapted from neubot/api/server.py

#
# Fetch and process common options from the query
# string, for now the only implemented option is
# debug, which modifies the semantic to return text
# for humans instead of JSON.
#

mimetype = 'application/json'
indent = None
sort_keys = False

options = cgi.parse_qs(query)

if utils.intify(options.get('debug', ['0'])[0]):
mimetype = 'text/plain'
indent = 4
sort_keys = True

#
# Now that we know the response format, decide what is
# the content of the response. If the labels option is
# available we return the documentation coupled with a
# setting. When the method is not POST, return instead
# the name and value of each setting.
#

if utils.intify(options.get('labels', ['0'])[0]):
obj = CONFIG.descriptions
elif request.method != 'POST':
obj = CONFIG.conf
else:

#
# When the method is POST we need to read the
# new settings from the request body. Settings
# are a x-www-form-urlencoded dictionary to
# ease AJAX programming.
#

body = request.body.read()
updates = marshal.qs_to_dictionary(body)

#
# PRE-update checks. We need to make sure that
# the following things are True:
#
# 1. that the incoming dictionary does not contain
# invalid privacy settings;
#
# 2. that the interval between automatic tests is
# either reasonable or set to zero, which means
# that it needs to be extracted randomly.
#

count = privacy.count_valid(updates, 'privacy.')
if count < 0:
raise ConfigError('Passed invalid privacy settings')

agent_interval = int(updates.get('agent.interval', 0))
if agent_interval != 0 and agent_interval < 1380:
raise ConfigError('Passed invalid agent.interval')

# Merge settings
CONFIG.merge_api(updates, DATABASE.connection())

#
# Update the state, such that, if the AJAX code is
# tracking the state it gets a notification that
# some configurations variable have been modified.
# Given that we communicate the update via that
# channel, the response body is an empty dict to
# keep happy the AJAX code.
#

STATE.update('config', updates)
obj = '{}'

#
# Now that we know the body, prepare and send
# the response for the client.
#

response = Message()

body = json.dumps(obj, sort_keys=sort_keys, indent=indent)
response.compose(code="200", reason="Ok", body=body, mimetype=mimetype)
stream.send_response(request, response)
2 changes: 1 addition & 1 deletion neubot/www/js/settings.js
Expand Up @@ -98,7 +98,7 @@ function settings_init() {
utils.setActiveTab("settings");

jQuery.ajax({
url: 'api/configlabels',
url: 'api/config?labels=1',
dataType: 'json',
success: function(data) {
labels = data;
Expand Down

0 comments on commit bd084f7

Please sign in to comment.