This repository was archived by the owner on Nov 4, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 168
feat: api v2 for mdn observatory #522
Open
LeoMcA
wants to merge
4
commits into
main
Choose a base branch
from
api-v2
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import sys | ||
from datetime import datetime, timedelta | ||
|
||
from flask import Blueprint, jsonify, request | ||
|
||
import httpobs.database as database | ||
import httpobs.scanner as scanner | ||
from httpobs import STATE_FAILED | ||
from httpobs.conf import API_COOLDOWN, DEVELOPMENT_MODE | ||
from httpobs.scanner.grader import get_score_description | ||
from httpobs.website import add_response_headers | ||
from httpobs.website.utils import valid_hostname | ||
|
||
api_v2 = Blueprint("api_v2", __name__) | ||
|
||
|
||
@api_v2.route("/analyze", methods=["GET", "OPTIONS", "POST"]) | ||
@add_response_headers(cors=True) | ||
def api_post_scan_hostname(): | ||
status_code = 200 | ||
scan = {} | ||
tests = {} | ||
|
||
host = request.args.get("host", "").lower().strip() | ||
try: | ||
site_id = database.select_site_id(host, create=False) | ||
except IOError: | ||
return { | ||
"error": "database-down", | ||
"text": "Unable to connect to database", | ||
}, 503 | ||
|
||
if site_id is not None: | ||
hostname = host | ||
else: | ||
ip = True if valid_hostname(host) is None else False | ||
if ip: | ||
return { | ||
"error": "invalid-hostname-ip", | ||
"text": "Cannot scan IP addresses", | ||
}, 400 | ||
|
||
hostname = valid_hostname(host) or ( | ||
valid_hostname("www." + host) if host else False | ||
) # prepend www. if necessary | ||
if not hostname: | ||
return { | ||
"error": "invalid-hostname", | ||
"text": f"{host} is an invalid hostname", | ||
}, 400 | ||
|
||
site_id: int = database.select_site_id(host, create=True) | ||
scan = database.select_scan_most_recent_scan(site_id) | ||
|
||
if scan and request.method == "POST": | ||
time_since_scan = datetime.now() - scan["end_time"] | ||
if time_since_scan < timedelta(seconds=API_COOLDOWN): | ||
status_code = 429 # keep going, we'll respond with the most recent scan | ||
else: | ||
scan = None # clear the scan, and we'll do another | ||
|
||
if scan: | ||
scan_id = scan["id"] | ||
|
||
tests = database.select_test_results(scan_id) | ||
for name, test in tests.items(): | ||
del test["id"] | ||
del test["scan_id"] | ||
del test["site_id"] | ||
del test["name"] | ||
test["score_description"] = get_score_description(test["result"]) | ||
tests[name] = {**test.pop("output"), **test} | ||
|
||
else: | ||
# no scan means we're a POST which hasn't been rate limited | ||
# or we're a GET for a host which has no scans in the db | ||
# either way, we need to perform a scan | ||
|
||
hidden = request.form.get("hidden", "false") == "true" | ||
|
||
scan = database.insert_scan(site_id, hidden=hidden) | ||
scan_id = scan["id"] | ||
|
||
# Get the site's cookies and headers | ||
# TODO: add API to insert these into the db | ||
# headers = database.select_site_headers(hostname) | ||
|
||
try: | ||
result = scanner.scan(hostname) | ||
|
||
if "error" in result: | ||
scan = database.update_scan_state(scan_id, STATE_FAILED, error=result["error"]) | ||
else: | ||
scan = database.insert_test_results( | ||
site_id, | ||
scan_id, | ||
result, | ||
) | ||
tests = result["tests"] | ||
except: | ||
# If we are unsuccessful, close out the scan in the database | ||
scan = database.update_scan_state(scan_id, STATE_FAILED) | ||
|
||
# Print the exception to stderr if we're in dev | ||
if DEVELOPMENT_MODE: | ||
import traceback | ||
|
||
print("Error detected in scan for: " + hostname) | ||
traceback.print_exc(file=sys.stderr) | ||
|
||
scan["start_time"] = scan["start_time"].isoformat() | ||
scan["end_time"] = scan["end_time"].isoformat() | ||
|
||
history = database.select_scan_host_history(site_id) | ||
|
||
# Prune for when the score doesn't change; thanks to chuck for the elegant list comprehension | ||
history = [ | ||
{ | ||
"end_time": v["end_time"].isoformat(), | ||
"grade": v["grade"], | ||
"id": v["scan_id"], | ||
"score": v["score"], | ||
} | ||
for k, v in enumerate(history) | ||
if history[k].get('score') is not history[k - 1].get('score') or k == 0 | ||
] | ||
|
||
return ( | ||
jsonify( | ||
{ | ||
"scan": scan, | ||
"tests": tests, | ||
"history": history, | ||
} | ||
), | ||
status_code, | ||
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,34 +1,32 @@ | ||
import sys | ||
|
||
from flask import Flask | ||
|
||
from httpobs.conf import API_PORT, API_PROPAGATE_EXCEPTIONS, DEVELOPMENT_MODE | ||
from httpobs.website import add_response_headers | ||
from httpobs.website.api import api | ||
from httpobs.website.monitoring import monitoring_api | ||
|
||
|
||
def __exit_with(msg: str) -> None: | ||
print(msg) | ||
sys.exit(1) | ||
|
||
def create_app(): | ||
# Register the application with flask | ||
app = Flask('http-observatory') | ||
app.config['PROPAGATE_EXCEPTIONS'] = API_PROPAGATE_EXCEPTIONS | ||
|
||
# Register the application with flask | ||
app = Flask('http-observatory') | ||
app.config['PROPAGATE_EXCEPTIONS'] = API_PROPAGATE_EXCEPTIONS | ||
app.register_blueprint(api) | ||
app.register_blueprint(monitoring_api) | ||
from httpobs.website.api import api | ||
from httpobs.website.api_v2 import api_v2 | ||
from httpobs.website.monitoring import monitoring_api | ||
|
||
app.register_blueprint(api) | ||
app.register_blueprint(api_v2, url_prefix="/api/v2") | ||
app.register_blueprint(monitoring_api) | ||
|
||
@app.route('/') | ||
@add_response_headers() | ||
def main() -> str: | ||
return 'Welcome to the HTTP Observatory!' | ||
return app | ||
|
||
|
||
def run(): | ||
app = create_app() | ||
app.run(debug=DEVELOPMENT_MODE, port=API_PORT) | ||
|
||
|
||
if __name__ == '__main__': | ||
run() | ||
|
||
# make backwards compatible with uwsgi setup | ||
# TODO: move into wsgi.py | ||
app = create_app() |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.